speccoder_runtime/format/speckit/
texts.rs1use std::path::Path;
5
6use crate::format::{Assess, Messages, Planning, Rules, Wording};
7
8pub const MARKER: &str = ".specify";
10pub const POINTER: &str = ".specify/feature.json";
12
13pub const FEATURE_ENV: &str = "SPECIFY_FEATURE_DIRECTORY";
15
16pub const CONSTITUTION: &str = ".specify/memory/constitution.md";
18
19pub static RULES: Rules = Rules {
21 intent: &[".specify", ".specify/**", "specs/**", "**/.specify/**"],
22 intent_reason: "it is Spec Kit intent (.specify/, specs/): the spec is not edited during implementation",
23 authoring_path: is_authoring_path,
24 command,
25 env: &[],
26 authoring_refusal,
27};
28
29fn is_authoring_path(p: &str) -> bool {
32 p.starts_with("specs/") || p == ".specify/feature.json" || p.starts_with(".specify/memory/")
33}
34
35fn authoring_refusal(p: &str) -> String {
36 format!(
37 "a planning skill writes Spec Kit artifacts (specs/, .specify/feature.json, .specify/memory/); `{p}` is not one"
38 )
39}
40
41fn command(argv: &[String], authoring: bool) -> Option<Result<(), String>> {
43 (authoring && is_script(argv)).then_some(Ok(()))
44}
45
46fn is_script(argv: &[String]) -> bool {
48 let script = |a: &str| {
49 let a = a.strip_prefix("./").unwrap_or(a);
50 let ext_script = a
54 .strip_prefix(".specify/extensions/")
55 .is_some_and(|rest| rest.split('/').nth(1) == Some("scripts"));
56 !a.contains("..")
57 && (a.starts_with(".specify/scripts/bash/") && a.ends_with(".sh")
58 || a.starts_with(".specify/scripts/powershell/") && a.ends_with(".ps1")
59 || ext_script && (a.ends_with(".sh") || a.ends_with(".ps1")))
60 };
61 match argv {
62 [first, ..] if script(first) => true,
63 [shell, second, ..] if matches!(shell.as_str(), "bash" | "sh") => script(second),
64 _ => false,
65 }
66}
67
68pub static WORDING: Wording = Wording {
70 product: "GitHub Spec Kit",
71 name: "Spec Kit",
72 unit: "feature",
73 intent_paths: ".specify/, specs/",
74 intent_code: "`.specify/`, `specs/`",
75 documents: "spec.md, plan.md and tasks.md",
76 authoring: "write Spec Kit artifacts (specs/, .specify/feature.json, .specify/memory/) and run Spec Kit's own scripts under .specify/scripts/",
77 authoring_tool: "a Spec Kit script",
78 story: "User story",
79 constitution: "Project constitution",
80 plan: "Feature plan",
81 plan_short: "Plan",
82 examples: "the acceptance examples of the task's user story",
83 convergence_task: "A task in a Convergence phase",
84 files: ["spec.md", "plan.md", "tasks.md"],
85 assess: Assess {
86 rules: "the constitution",
87 targets: "functional requirement (FR-###), success criterion that needs buildable work (SC-###), user story acceptance scenario (USn/ACm), plan decision and constitution MUST principle",
88 must: "a constitution MUST",
89 sources: "spec, plan and tasks",
90 critical: "constitution MUST violation, or missing/contradicts that blocks a P1 story",
91 refs: "FR-003, US1/AC2, plan: storage, Constitution II",
92 },
93};
94
95pub static MESSAGES: Messages = Messages {
97 tasks_file: "tasks.md",
98 make_tasks: "`speccoder spec tasks` writes it",
99 not_a_project,
100 no_project: "not a Spec Kit project (no .specify/ found)",
101 no_project_in_worktree: "the worktree has no Spec Kit project",
102 not_in_git: "the Spec Kit project is not inside a git repository",
103 no_skills: "no skills installed (Spec Kit installs them with `specify init --here --integration claude`, under .claude/skills/)",
104 checklists,
105 unit: "feature",
106 no_current: "",
107 current_shared: "Spec Kit's scripts and the /speckit-* commands in your editor read it too",
108 make_project: (
109 "specify init --here --integration claude",
110 "make it a Spec Kit project (the skills speccoder runs come with it)",
111 ),
112};
113
114fn not_a_project(path: &Path) -> String {
115 format!(
116 "no Spec Kit project (.specify/) at or above {}; run `specify init --here --integration claude` first (speccoder runs Spec Kit's skills from .claude/skills/)",
117 path.display()
118 )
119}
120
121fn checklists(open: &str) -> String {
122 format!(
123 "unfinished checklists: {open}; Spec Kit treats an unchecked item as a stop — finish them, or run with --ignore-checklists"
124 )
125}
126
127pub static PLANNING: Planning = Planning {
129 prefix: "speckit-",
130 core: &[
133 "specify",
134 "clarify",
135 "plan",
136 "tasks",
137 "analyze",
138 "checklist",
139 "constitution",
140 ],
141 refused,
142 creates: &["specify"],
143 current_env: Some(FEATURE_ENV),
144 toward_tasks,
145 revises: Some("clarify"),
146};
147
148fn toward_tasks(has_plan: bool) -> &'static [&'static str] {
149 if has_plan {
150 &["tasks"]
151 } else {
152 &["plan", "tasks"]
153 }
154}
155
156fn refused(name: &str) -> Option<String> {
158 match name {
159 "speckit-implement" => Some(
160 "speccoder implements tasks itself, with evidence: use `speccoder run`".to_owned(),
161 ),
162 "speckit-converge" => Some(
163 "speccoder converges with a read-only assessment and a guarded append: use `speccoder converge`"
164 .to_owned(),
165 ),
166 "speckit-taskstoissues" => Some(
167 "it turns tasks into GitHub issues; speccoder goes the other way — `speccoder issue start N` makes a feature from an issue, and `speccoder pr` closes it".to_owned(),
168 ),
169 _ => None,
170 }
171}