@trieungoctam/speckit 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +12 -0
  2. package/dist/cli.js +22 -1
  3. package/dist/commands/doctor.js +12 -9
  4. package/dist/commands/graph.js +4 -2
  5. package/dist/commands/init.js +4 -2
  6. package/dist/commands/plan.js +37 -0
  7. package/dist/commands/quick.js +38 -0
  8. package/dist/commands/ready.js +6 -4
  9. package/dist/commands/score.d.ts +7 -0
  10. package/dist/commands/score.js +29 -0
  11. package/dist/commands/setup.js +24 -21
  12. package/dist/commands/sync.js +4 -2
  13. package/dist/commands/team.d.ts +10 -0
  14. package/dist/commands/team.js +129 -0
  15. package/dist/commands/validate.js +4 -2
  16. package/dist/core/colors.d.ts +11 -0
  17. package/dist/core/colors.js +40 -0
  18. package/dist/core/scaffold.js +2 -0
  19. package/dist/core/spec-score.d.ts +16 -0
  20. package/dist/core/spec-score.js +112 -0
  21. package/dist/core/team-report.d.ts +19 -0
  22. package/dist/core/team-report.js +149 -0
  23. package/dist/core/team-scaffold.d.ts +2 -0
  24. package/dist/core/team-scaffold.js +92 -0
  25. package/dist/core/templates.d.ts +1 -1
  26. package/dist/core/templates.js +15 -0
  27. package/docs/adapters.md +12 -0
  28. package/docs/code-standards.md +2 -0
  29. package/docs/development-roadmap.md +7 -3
  30. package/docs/enterprise-rollout.md +8 -3
  31. package/docs/product-contract.md +21 -2
  32. package/docs/project-changelog.md +31 -0
  33. package/docs/release-checklist.md +4 -0
  34. package/docs/spec-quality-gates.md +27 -0
  35. package/docs/system-architecture.md +19 -1
  36. package/docs/team-workflow.md +40 -0
  37. package/docs/use-cases.md +11 -0
  38. package/docs/workflow-model.md +31 -0
  39. package/package.json +1 -1
package/README.md CHANGED
@@ -23,6 +23,10 @@ npx @trieungoctam/speckit@latest sync
23
23
  npx @trieungoctam/speckit@latest graph setup
24
24
  npx @trieungoctam/speckit@latest sprint plan
25
25
  npx @trieungoctam/speckit@latest graph triage --json
26
+ npx @trieungoctam/speckit@latest team status .speckit/stories/<story>.md
27
+ npx @trieungoctam/speckit@latest team audit .speckit/stories/<story>.md --json
28
+ npx @trieungoctam/speckit@latest team handoff .speckit/stories/<story>.md --from dev --to qa
29
+ npx @trieungoctam/speckit@latest score .speckit/stories/<story>.md
26
30
  npx @trieungoctam/speckit@latest validate --json
27
31
  npx @trieungoctam/speckit@latest permissions audit --path .env --json
28
32
  npx @trieungoctam/speckit@latest ready .speckit/stories/<story>.md
@@ -30,6 +34,8 @@ npx @trieungoctam/speckit@latest session checkpoint --note "red complete"
30
34
  npx @trieungoctam/speckit@latest review
31
35
  ```
32
36
 
37
+ Interactive CLI output is colorized when the terminal supports ANSI colors. Use `NO_COLOR=1` to disable colors, or `SPECKIT_COLOR=1` to force colors for demos and snapshots.
38
+
33
39
  Or install globally:
34
40
 
35
41
  ```bash
@@ -83,6 +89,11 @@ For implementation stories, red-green-refactor evidence is mandatory. A story is
83
89
  | `speckit session compact` | Create an anchored summary for resume or handoff. |
84
90
  | `speckit session resume` | Mark a session as active and print its handoff path. |
85
91
  | `speckit session status` | Print active session state paths. |
92
+ | `speckit team status <story>` | Show role-by-role team artifact readiness for a story. |
93
+ | `speckit team audit <story>` | Run enterprise review-gate checks and return nonzero when blockers remain. |
94
+ | `speckit team handoff <story>` | Create a durable role-to-role handoff artifact. |
95
+ | `speckit score` | Score project workflow health across contract, team model, context/session, and graph sync. |
96
+ | `speckit score <story>` | Score story health across team readiness, developer trace, TDD evidence, and review gate. |
86
97
  | `speckit shape "<intent>"` | Create a short spec contract. |
87
98
  | `speckit context <story>` | Build the current implementation context from a story. |
88
99
  | `speckit quick "<intent>"` | Create one story plus matching TDD evidence file. |
@@ -116,6 +127,7 @@ See `docs/adapters.md` for exact output paths.
116
127
  ## Guides
117
128
 
118
129
  - `docs/use-cases.md` covers setup, migration, quick changes, full planning, long sessions, TDD, graph automation, review, CI, and troubleshooting.
130
+ - `docs/team-workflow.md` covers enterprise role ownership, status, audit, and handoff flow.
119
131
  - `docs/beads-viewer-setup.md` covers Beads Viewer installation, `bv` verification, and robot-safe graph usage.
120
132
  - `docs/workflow-model.md` describes the quick and full workflow lanes.
121
133
  - `docs/prompt-architecture.md` describes the prompt contract used by the super-agent, skills, workflows, run prompt, and IDE adapters.
package/dist/cli.js CHANGED
@@ -9,6 +9,8 @@ import { planCommand } from "./commands/plan.js";
9
9
  import { memoryCommand } from "./commands/memory.js";
10
10
  import { permissionsCommand } from "./commands/permissions.js";
11
11
  import { sessionCommand } from "./commands/session.js";
12
+ import { teamCommand } from "./commands/team.js";
13
+ import { scoreCommand } from "./commands/score.js";
12
14
  import { sprintCommand } from "./commands/sprint.js";
13
15
  import { graphCommand } from "./commands/graph.js";
14
16
  import { validateCommand } from "./commands/validate.js";
@@ -19,6 +21,7 @@ import { runCommand } from "./commands/run.js";
19
21
  import { readyCommand } from "./commands/ready.js";
20
22
  import { reviewCommand } from "./commands/review.js";
21
23
  import { closeCommand } from "./commands/close.js";
24
+ import { createColors } from "./core/colors.js";
22
25
  export async function main(argv = process.argv.slice(2), root = process.cwd()) {
23
26
  const parsed = parseArgs(argv);
24
27
  try {
@@ -65,6 +68,21 @@ export async function main(argv = process.argv.slice(2), root = process.cwd()) {
65
68
  note: value(parsed, "note"),
66
69
  json: has(parsed, "json"),
67
70
  });
71
+ case "team":
72
+ return teamCommand({
73
+ root,
74
+ action: requiredAction(parsed),
75
+ target: parsed.args.slice(1).join(" ").trim() || undefined,
76
+ from: value(parsed, "from"),
77
+ to: value(parsed, "to"),
78
+ json: has(parsed, "json"),
79
+ });
80
+ case "score":
81
+ return scoreCommand({
82
+ root,
83
+ target: parsed.args.join(" ").trim() || undefined,
84
+ json: has(parsed, "json"),
85
+ });
68
86
  case "sprint":
69
87
  return sprintCommand({ root, action: requiredAction(parsed), json: has(parsed, "json") });
70
88
  case "graph":
@@ -145,7 +163,8 @@ function isHelp(parsed) {
145
163
  return parsed.command === "help" || parsed.command === "--help" || parsed.command === "-h" || has(parsed, "help");
146
164
  }
147
165
  function printHelp() {
148
- console.log(`Speckit - Agile + TDD workflow compiler for agentic IDEs
166
+ const colors = createColors();
167
+ console.log(`${colors.bold(colors.cyan("Speckit"))} - Agile + TDD workflow compiler for agentic IDEs
149
168
 
150
169
  Usage:
151
170
  speckit setup
@@ -160,6 +179,8 @@ Usage:
160
179
  speckit memory refresh
161
180
  speckit permissions audit [--path <path>] [--command <command>] [--json]
162
181
  speckit session start|checkpoint|compact|resume|status [target] [--note "..."] [--json]
182
+ speckit team status|audit|handoff <story-path-or-id> [--from <role>] [--to <role>] [--json]
183
+ speckit score [story-path-or-id] [--json]
163
184
  speckit sprint plan|next [--json]
164
185
  speckit graph setup|triage|plan|insights [--json]
165
186
  speckit validate [--json]
@@ -6,8 +6,10 @@ import { detectTestCommands } from "../core/test-detection.js";
6
6
  import { coreFiles, enterpriseFiles } from "../core/scaffold.js";
7
7
  import { agentFiles } from "../core/agent-scaffold.js";
8
8
  import { validateWorkflowContract } from "../core/workflow-validator.js";
9
+ import { createColors } from "../core/colors.js";
9
10
  export async function doctorCommand(options) {
10
11
  const stdout = options.stdout ?? console;
12
+ const colors = createColors();
11
13
  const tools = checkTools();
12
14
  const tests = await detectTestCommands(options.root);
13
15
  const deepChecks = options.deep ? await runDeepChecks(options.root) : [];
@@ -29,24 +31,25 @@ export async function doctorCommand(options) {
29
31
  stdout.log(JSON.stringify(report, null, 2));
30
32
  }
31
33
  else {
32
- stdout.log(`Speckit doctor: ${report.status}`);
33
- stdout.log(`Node: ${report.node}`);
34
- stdout.log(`Tools: ${tools.map((tool) => `${tool.name}=${tool.available ? "ok" : "missing"}`).join(", ")}`);
34
+ stdout.log(`${colors.bold("Speckit doctor")}: ${colors.status(report.status === "ok", "ok", report.status)}`);
35
+ stdout.log(`${colors.bold("Node")}: ${report.node}`);
36
+ stdout.log(`Tools: ${tools.map((tool) => `${tool.name}=${colors.status(tool.available)}`).join(", ")}`);
35
37
  for (const tool of tools.filter((tool) => !tool.available)) {
36
- stdout.log(`Tool hint ${tool.name}: ${tool.hint}`);
38
+ stdout.log(`${colors.yellow("Tool hint")} ${tool.name}: ${tool.hint}`);
37
39
  if (tool.install?.length) {
38
- stdout.log(`Install ${tool.name}: ${tool.install.join(" OR ")}`);
40
+ stdout.log(`${colors.cyan(`Install ${tool.name}`)}: ${tool.install.join(" OR ")}`);
39
41
  }
40
42
  }
41
- stdout.log(`Tests: ${tests[0]?.command ?? "not detected"}`);
43
+ stdout.log(`${colors.bold("Tests")}: ${tests[0]?.command ?? colors.yellow("not detected")}`);
42
44
  for (const check of deepChecks) {
43
- stdout.log(`Deep ${check.name}: ${check.ok ? "ok" : "missing"}`);
45
+ stdout.log(`Deep ${check.name}: ${colors.status(check.ok)}`);
44
46
  }
45
47
  for (const check of contractChecks) {
46
- stdout.log(`Contract ${check.name}: ${check.ok ? "ok" : check.detail}`);
48
+ stdout.log(`Contract ${check.name}: ${check.ok ? colors.green("ok") : colors.red(check.detail)}`);
47
49
  }
48
50
  for (const adapter of adapters) {
49
- stdout.log(`Adapter ${adapter.name}: ${adapter.present}/${adapter.total} files present`);
51
+ const ok = adapter.present === adapter.total;
52
+ stdout.log(`Adapter ${adapter.name}: ${colors.status(ok, `${adapter.present}/${adapter.total}`, `${adapter.present}/${adapter.total}`)} files present`);
50
53
  }
51
54
  }
52
55
  return report.status === "ok" ? 0 : 1;
@@ -1,6 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { beadsViewerInstallGuide, checkTools, commandExists } from "../adapters/tool-checks.js";
3
3
  import { prepareBeadsMirror } from "../core/beads-mirror.js";
4
+ import { createColors } from "../core/colors.js";
4
5
  import { readSyncedStories, selectableStories } from "../core/synced-stories.js";
5
6
  const robotActions = new Map([
6
7
  ["triage", "--robot-triage"],
@@ -37,6 +38,7 @@ export async function graphCommand(options) {
37
38
  }
38
39
  async function graphSetup(options) {
39
40
  const stdout = options.stdout ?? console;
41
+ const colors = createColors();
40
42
  const tools = checkTools().filter((tool) => ["br", "bd", "bv"].includes(tool.name));
41
43
  const mirrorPath = await prepareBeadsMirror(options.root);
42
44
  const report = {
@@ -51,9 +53,9 @@ async function graphSetup(options) {
51
53
  else {
52
54
  stdout.log(beadsViewerInstallGuide());
53
55
  stdout.log("");
54
- stdout.log(`Mirror: ${mirrorPath}`);
56
+ stdout.log(`${colors.bold("Mirror")}: ${colors.cyan(mirrorPath)}`);
55
57
  for (const tool of tools) {
56
- stdout.log(`${tool.name}: ${tool.available ? "ok" : "missing"} - ${tool.hint}`);
58
+ stdout.log(`${colors.bold(tool.name)}: ${colors.status(tool.available)} - ${colors.dim(tool.hint)}`);
57
59
  }
58
60
  }
59
61
  return 0;
@@ -2,8 +2,10 @@ import { coreFiles, enterpriseFiles } from "../core/scaffold.js";
2
2
  import { agentFiles } from "../core/agent-scaffold.js";
3
3
  import { writeManagedFiles } from "../core/managed-files.js";
4
4
  import { getAdapters } from "../config/adapter-registry.js";
5
+ import { createColors } from "../core/colors.js";
5
6
  export async function initCommand(options) {
6
7
  const stdout = options.stdout ?? console;
8
+ const colors = createColors();
7
9
  const selectedIde = options.ide ?? "all";
8
10
  const sharedFiles = [...coreFiles(), ...agentFiles()];
9
11
  const baseFiles = options.enterprise ? [...sharedFiles, ...enterpriseFiles()] : sharedFiles;
@@ -13,9 +15,9 @@ export async function initCommand(options) {
13
15
  const updated = results.filter((result) => result.status === "updated").length;
14
16
  const skipped = results.filter((result) => result.status === "skipped");
15
17
  const mode = options.enterprise ? "enterprise" : "standard";
16
- stdout.log(`Speckit initialized (${mode}): ${created} created, ${updated} updated, ${skipped.length} skipped.`);
18
+ stdout.log(`${colors.bold("Speckit initialized")} (${colors.cyan(mode)}): ${colors.green(String(created))} created, ${colors.yellow(String(updated))} updated, ${colors.status(skipped.length === 0, String(skipped.length), String(skipped.length))} skipped.`);
17
19
  for (const result of skipped) {
18
- stdout.log(`skipped ${result.path}: ${result.reason}`);
20
+ stdout.log(`${colors.yellow("skipped")} ${result.path}: ${result.reason}`);
19
21
  }
20
22
  return skipped.length > 0 ? 2 : 0;
21
23
  }
@@ -40,16 +40,49 @@ export async function planCommand(options) {
40
40
  evidence: ${dir}/tdd-evidence.md
41
41
  context: pending`, `# Story: ${options.intent}
42
42
 
43
+ ## Business Goal
44
+ - Goal: ${options.intent}
45
+ - Priority: medium
46
+ - Success signal:
47
+
43
48
  ## Acceptance Criteria
44
49
  - Given ...
45
50
  - When ...
46
51
  - Then ...
47
52
 
53
+ ## Edge Cases
54
+ - Boundary:
55
+ - Error path:
56
+ - State transition:
57
+
58
+ ## Implementation Scope
59
+ - In scope:
60
+ - Out of scope:
61
+ - Files likely to read:
62
+ - Files likely to modify:
63
+
64
+ ## Architecture Notes
65
+ - Constraints:
66
+ - Integration points:
67
+ - Risks:
68
+
48
69
  ## TDD Evidence File
49
70
  \`${dir}/tdd-evidence.md\`
50
71
 
51
72
  ## Implementation Notes
52
73
 
74
+ ## Dev Agent Record
75
+ ### Test Intent
76
+
77
+ ### Debug Log
78
+
79
+ ### Completion Notes
80
+
81
+ ### File List
82
+
83
+ ## Change Log
84
+ - ${new Date().toISOString().slice(0, 10)}: Story drafted.
85
+
53
86
  ## Spec Anti-Mistake Checklist
54
87
  - Reuse existing project patterns before adding new files.
55
88
  - Verify file locations before editing.
@@ -62,11 +95,15 @@ context: pending`, `# Story: ${options.intent}
62
95
  content: markdownWithFrontmatter(`status: missing
63
96
  story: ${dir}/story.md`, `# TDD Evidence: ${options.intent}
64
97
 
98
+ ## Test Intent
99
+
65
100
  ## Red
66
101
 
67
102
  ## Green
68
103
 
69
104
  ## Refactor
105
+
106
+ ## Review Evidence
70
107
  `),
71
108
  },
72
109
  ];
@@ -18,11 +18,32 @@ context: pending`, `# Story: ${options.intent}
18
18
  ## Intent
19
19
  ${options.intent}
20
20
 
21
+ ## Business Goal
22
+ - Goal: ${options.intent}
23
+ - Priority: medium
24
+ - Success signal: behavior is covered by executable tests and Speckit evidence
25
+
21
26
  ## Acceptance Criteria
22
27
  - Given the current product state
23
28
  - When this story is implemented
24
29
  - Then the behavior is covered by executable tests and Speckit evidence
25
30
 
31
+ ## Edge Cases
32
+ - Boundary: document the smallest input, state, or permission boundary this story touches.
33
+ - Error path: document how failures are surfaced or recovered.
34
+ - State transition: document before and after behavior.
35
+
36
+ ## Implementation Scope
37
+ - In scope: the smallest change needed to satisfy the acceptance criteria.
38
+ - Out of scope: unrelated refactors, new dependencies, or behavior not tied to acceptance criteria.
39
+ - Files likely to read:
40
+ - Files likely to modify:
41
+
42
+ ## Architecture Notes
43
+ - Constraints: reuse existing project patterns before adding new abstractions.
44
+ - Integration points:
45
+ - Risks:
46
+
26
47
  ## TDD Checklist
27
48
  - [ ] Test target identified
28
49
  - [ ] Red evidence recorded in ${evidencePath}
@@ -38,6 +59,18 @@ ${options.intent}
38
59
  - Do not introduce new libraries without explicit need.
39
60
  - Preserve existing behavior unless an acceptance criterion requires change.
40
61
  - Update docs only when behavior or workflow changes.
62
+
63
+ ## Dev Agent Record
64
+ ### Test Intent
65
+
66
+ ### Debug Log
67
+
68
+ ### Completion Notes
69
+
70
+ ### File List
71
+
72
+ ## Change Log
73
+ - ${new Date().toISOString().slice(0, 10)}: Story drafted.
41
74
  `),
42
75
  },
43
76
  {
@@ -58,6 +91,11 @@ story: ${storyPath}`, `# TDD Evidence: ${options.intent}
58
91
  ## Refactor
59
92
  - Command: \`${testCommand}\`
60
93
  - Result:
94
+
95
+ ## Review Evidence
96
+ - Reviewer:
97
+ - Outcome:
98
+ - Follow-ups:
61
99
  `),
62
100
  },
63
101
  ]);
@@ -1,20 +1,22 @@
1
1
  import { evaluateReadiness } from "../core/readiness.js";
2
+ import { createColors } from "../core/colors.js";
2
3
  export async function readyCommand(options) {
3
4
  const stdout = options.stdout ?? console;
5
+ const colors = createColors();
4
6
  const report = await evaluateReadiness(options.root, options.target);
5
7
  if (options.json) {
6
8
  stdout.log(JSON.stringify(report, null, 2));
7
9
  return report.status === "ready" ? 0 : 1;
8
10
  }
9
- stdout.log(`# Speckit Readiness: ${report.status}`);
11
+ stdout.log(`# ${colors.bold("Speckit Readiness")}: ${colors.status(report.status === "ready", "ready", report.status)}`);
10
12
  if (report.story) {
11
- stdout.log(`Story: ${report.story.path}`);
13
+ stdout.log(`${colors.bold("Story")}: ${colors.cyan(report.story.path)}`);
12
14
  }
13
15
  for (const check of report.checks) {
14
- stdout.log(`- ${check.ok ? "ok" : "blocked"} ${check.name}: ${check.detail}`);
16
+ stdout.log(`- ${colors.status(check.ok, "ok", "blocked")} ${check.name}: ${check.detail}`);
15
17
  }
16
18
  if (report.status !== "ready") {
17
- stdout.error("Run the suggested setup commands before `speckit run`.");
19
+ stdout.error(colors.yellow("Run the suggested setup commands before `speckit run`."));
18
20
  }
19
21
  return report.status === "ready" ? 0 : 1;
20
22
  }
@@ -0,0 +1,7 @@
1
+ export type ScoreOptions = {
2
+ root: string;
3
+ target?: string;
4
+ json?: boolean;
5
+ stdout?: Pick<typeof console, "log">;
6
+ };
7
+ export declare function scoreCommand(options: ScoreOptions): Promise<number>;
@@ -0,0 +1,29 @@
1
+ import { createColors } from "../core/colors.js";
2
+ import { calculateSpecScore } from "../core/spec-score.js";
3
+ export async function scoreCommand(options) {
4
+ const stdout = options.stdout ?? console;
5
+ const report = await calculateSpecScore(options.root, options.target);
6
+ stdout.log(options.json ? JSON.stringify(report, null, 2) : renderScore(report));
7
+ return report.status === "blocked" ? 1 : 0;
8
+ }
9
+ function renderScore(report) {
10
+ const colors = createColors();
11
+ const lines = [
12
+ `${colors.bold("Spec Runtime Score")}: ${colors.status(report.score >= 75, `${report.score}/100`, `${report.score}/100`)}`,
13
+ `Status: ${report.status}`,
14
+ ];
15
+ if (report.target)
16
+ lines.push(`Target: ${colors.cyan(report.target)}`);
17
+ lines.push("");
18
+ for (const category of report.categories) {
19
+ const ok = category.score >= 75;
20
+ lines.push(`${colors.status(ok, String(category.score), String(category.score)).padStart(colors.enabled ? 12 : 3)} ${category.name}: ${category.passed}/${category.total} - ${category.detail}`);
21
+ }
22
+ lines.push("", "Recommendations:");
23
+ lines.push(...report.recommendations.map((item) => `- ${item}`));
24
+ if (report.blockers.length) {
25
+ lines.push("", "Blockers:");
26
+ lines.push(...report.blockers.map((item) => `- ${item}`));
27
+ }
28
+ return lines.join("\n");
29
+ }
@@ -1,5 +1,6 @@
1
1
  import { createInterface } from "node:readline/promises";
2
2
  import { adapters } from "../config/adapter-registry.js";
3
+ import { createColors } from "../core/colors.js";
3
4
  import { graphCommand } from "./graph.js";
4
5
  import { initCommand } from "./init.js";
5
6
  const modeChoices = [
@@ -17,6 +18,7 @@ export async function setupCommand(options) {
17
18
  const stdin = options.stdin ?? process.stdin;
18
19
  const output = options.output ?? process.stdout;
19
20
  const stdout = options.stdout ?? console;
21
+ const colors = createColors(output);
20
22
  if (options.answers) {
21
23
  return setupFromAnswers(options, stdout, options.answers);
22
24
  }
@@ -26,14 +28,14 @@ export async function setupCommand(options) {
26
28
  }
27
29
  const rl = createInterface({ input: stdin, output });
28
30
  try {
29
- stdout.log("Speckit setup");
31
+ stdout.log(colors.bold(colors.cyan("Speckit setup")));
30
32
  stdout.log("");
31
- const mode = await select(rl, stdout, modeChoices, "Choose setup mode", "1");
32
- const ide = await select(rl, stdout, ideChoices, "Choose IDE adapter", "1");
33
- const force = await confirm(rl, stdout, "Overwrite unmanaged files if needed?", false);
34
- const configureGraph = await confirm(rl, stdout, "Run Beads Viewer setup after init?", true);
33
+ const mode = await select(rl, stdout, modeChoices, "Choose setup mode", "1", colors);
34
+ const ide = await select(rl, stdout, ideChoices, "Choose IDE adapter", "1", colors);
35
+ const force = await confirm(rl, stdout, "Overwrite unmanaged files if needed?", false, colors);
36
+ const configureGraph = await confirm(rl, stdout, "Run Beads Viewer setup after init?", true, colors);
35
37
  stdout.log("");
36
- stdout.log(`Selected: ${mode}, ide=${ide}, force=${force ? "yes" : "no"}, graph=${configureGraph ? "yes" : "no"}`);
38
+ stdout.log(colors.dim(`Selected: ${mode}, ide=${ide}, force=${force ? "yes" : "no"}, graph=${configureGraph ? "yes" : "no"}`));
37
39
  stdout.log("");
38
40
  const initExit = await initCommand({
39
41
  root: options.root,
@@ -53,14 +55,15 @@ export async function setupCommand(options) {
53
55
  }
54
56
  }
55
57
  async function setupFromAnswers(options, stdout, answers) {
56
- stdout.log("Speckit setup");
58
+ const colors = createColors();
59
+ stdout.log(colors.bold(colors.cyan("Speckit setup")));
57
60
  stdout.log("");
58
- const mode = selectAnswer(stdout, modeChoices, "Choose setup mode", "1", answers.shift());
59
- const ide = selectAnswer(stdout, ideChoices, "Choose IDE adapter", "1", answers.shift());
61
+ const mode = selectAnswer(stdout, modeChoices, "Choose setup mode", "1", answers.shift(), colors);
62
+ const ide = selectAnswer(stdout, ideChoices, "Choose IDE adapter", "1", answers.shift(), colors);
60
63
  const force = confirmAnswer("Overwrite unmanaged files if needed?", false, answers.shift());
61
64
  const configureGraph = confirmAnswer("Run Beads Viewer setup after init?", true, answers.shift());
62
65
  stdout.log("");
63
- stdout.log(`Selected: ${mode}, ide=${ide}, force=${force ? "yes" : "no"}, graph=${configureGraph ? "yes" : "no"}`);
66
+ stdout.log(colors.dim(`Selected: ${mode}, ide=${ide}, force=${force ? "yes" : "no"}, graph=${configureGraph ? "yes" : "no"}`));
64
67
  stdout.log("");
65
68
  const initExit = await initCommand({
66
69
  root: options.root,
@@ -75,9 +78,9 @@ async function setupFromAnswers(options, stdout, answers) {
75
78
  }
76
79
  return initExit;
77
80
  }
78
- function selectAnswer(stdout, choices, prompt, defaultValue, answer) {
79
- stdout.log(prompt);
80
- choices.forEach((choice, index) => stdout.log(` ${index + 1}. ${choice.label}`));
81
+ function selectAnswer(stdout, choices, prompt, defaultValue, answer, colors = createColors()) {
82
+ stdout.log(colors.bold(prompt));
83
+ choices.forEach((choice, index) => stdout.log(` ${colors.cyan(String(index + 1))}. ${choice.label}`));
81
84
  const selected = (answer?.trim() || defaultValue);
82
85
  const index = Number.parseInt(selected, 10) - 1;
83
86
  if (!Number.isInteger(index) || !choices[index]) {
@@ -96,33 +99,33 @@ function confirmAnswer(prompt, defaultValue, answer) {
96
99
  return false;
97
100
  throw new Error(`Invalid yes/no answer for "${prompt}": ${answer}`);
98
101
  }
99
- async function select(rl, stdout, choices, prompt, defaultValue) {
102
+ async function select(rl, stdout, choices, prompt, defaultValue, colors = createColors()) {
100
103
  for (;;) {
101
- stdout.log(prompt);
104
+ stdout.log(colors.bold(prompt));
102
105
  choices.forEach((choice, index) => {
103
106
  const number = String(index + 1);
104
- stdout.log(` ${number}. ${choice.label}`);
107
+ stdout.log(` ${colors.cyan(number)}. ${choice.label}`);
105
108
  });
106
- const answer = (await rl.question(`Select [${defaultValue}]: `)).trim() || defaultValue;
109
+ const answer = (await rl.question(`${colors.dim("Select")} [${colors.cyan(defaultValue)}]: `)).trim() || defaultValue;
107
110
  const index = Number.parseInt(answer, 10) - 1;
108
111
  if (Number.isInteger(index) && choices[index]) {
109
112
  stdout.log("");
110
113
  return choices[index].value;
111
114
  }
112
- stdout.log(`Invalid selection: ${answer}`);
115
+ stdout.log(colors.red(`Invalid selection: ${answer}`));
113
116
  stdout.log("");
114
117
  }
115
118
  }
116
- async function confirm(rl, stdout, prompt, defaultValue) {
119
+ async function confirm(rl, stdout, prompt, defaultValue, colors = createColors()) {
117
120
  const suffix = defaultValue ? "Y/n" : "y/N";
118
121
  for (;;) {
119
- const answer = (await rl.question(`${prompt} [${suffix}]: `)).trim().toLowerCase();
122
+ const answer = (await rl.question(`${prompt} [${colors.cyan(suffix)}]: `)).trim().toLowerCase();
120
123
  if (!answer)
121
124
  return defaultValue;
122
125
  if (["y", "yes"].includes(answer))
123
126
  return true;
124
127
  if (["n", "no"].includes(answer))
125
128
  return false;
126
- stdout.log("Please answer y or n.");
129
+ stdout.log(colors.yellow("Please answer y or n."));
127
130
  }
128
131
  }
@@ -3,8 +3,10 @@ import { join } from "node:path";
3
3
  import { markdown, writeManagedFiles } from "../core/managed-files.js";
4
4
  import { firstHeading, frontmatterValue } from "../core/story.js";
5
5
  import { writeBeadsMirror } from "../core/beads-mirror.js";
6
+ import { createColors } from "../core/colors.js";
6
7
  export async function syncCommand(options) {
7
8
  const stdout = options.stdout ?? console;
9
+ const colors = createColors();
8
10
  const storiesDir = join(options.root, ".speckit", "stories");
9
11
  let entries;
10
12
  try {
@@ -45,7 +47,7 @@ bv --robot-next --format json
45
47
  },
46
48
  ], true);
47
49
  const beadsPath = await writeBeadsMirror(options.root, stories);
48
- stdout.log(`Synced ${stories.length} stories to .speckit/sync/beads-sync.jsonl`);
49
- stdout.log(`Prepared Beads Viewer mirror at ${beadsPath}`);
50
+ stdout.log(`Synced ${colors.cyan(String(stories.length))} stories to ${colors.cyan(".speckit/sync/beads-sync.jsonl")}`);
51
+ stdout.log(`Prepared Beads Viewer mirror at ${colors.cyan(beadsPath)}`);
50
52
  return 0;
51
53
  }
@@ -0,0 +1,10 @@
1
+ export type TeamOptions = {
2
+ root: string;
3
+ action: string;
4
+ target?: string;
5
+ from?: string;
6
+ to?: string;
7
+ json?: boolean;
8
+ stdout?: Pick<typeof console, "log" | "error">;
9
+ };
10
+ export declare function teamCommand(options: TeamOptions): Promise<number>;
@@ -0,0 +1,129 @@
1
+ import { buildTeamReport } from "../core/team-report.js";
2
+ import { createColors } from "../core/colors.js";
3
+ import { markdown, writeManagedFiles } from "../core/managed-files.js";
4
+ import { slugify, timestamp } from "../core/slug.js";
5
+ export async function teamCommand(options) {
6
+ const stdout = options.stdout ?? console;
7
+ switch (options.action) {
8
+ case "status":
9
+ return status(options);
10
+ case "audit":
11
+ return audit(options);
12
+ case "handoff":
13
+ return handoff(options);
14
+ default:
15
+ stdout.error("Usage: speckit team status|audit|handoff <story> [--from <role>] [--to <role>] [--json]");
16
+ return 1;
17
+ }
18
+ }
19
+ async function status(options) {
20
+ const stdout = options.stdout ?? console;
21
+ const target = requireTarget(options);
22
+ if (!target)
23
+ return 1;
24
+ const report = await buildTeamReport(options.root, target);
25
+ stdout.log(options.json ? JSON.stringify(report, null, 2) : renderReport(report, "Spec Team Status"));
26
+ return report.story ? 0 : 1;
27
+ }
28
+ async function audit(options) {
29
+ const stdout = options.stdout ?? console;
30
+ const target = requireTarget(options);
31
+ if (!target)
32
+ return 1;
33
+ const report = await buildTeamReport(options.root, target);
34
+ const auditReport = {
35
+ gate: "review",
36
+ status: report.status,
37
+ story: report.story,
38
+ blockers: report.blockers,
39
+ warnings: report.warnings,
40
+ roles: report.roles,
41
+ };
42
+ stdout.log(options.json ? JSON.stringify(auditReport, null, 2) : renderAudit(auditReport));
43
+ return report.status === "ok" ? 0 : 1;
44
+ }
45
+ async function handoff(options) {
46
+ const stdout = options.stdout ?? console;
47
+ const target = requireTarget(options);
48
+ if (!target)
49
+ return 1;
50
+ const report = await buildTeamReport(options.root, target);
51
+ if (!report.story) {
52
+ stdout.error(report.blockers[0] ?? `Story not found: ${target}`);
53
+ return 1;
54
+ }
55
+ const from = options.from ?? "current-role";
56
+ const to = options.to ?? "next-role";
57
+ const path = `.speckit/team/handoffs/${timestamp()}-${slugify(report.story.id)}-${slugify(from)}-to-${slugify(to)}.md`;
58
+ await writeManagedFiles(options.root, [
59
+ {
60
+ path,
61
+ content: markdown(`# Spec Team Handoff
62
+
63
+ ## Story
64
+ \`${report.story.path}\`
65
+
66
+ ## Route
67
+ - From: ${from}
68
+ - To: ${to}
69
+
70
+ ## Current Status
71
+ - Story status: ${report.story.status ?? "missing"}
72
+ - Team status: ${report.status}
73
+
74
+ ## Blockers
75
+ ${report.blockers.length ? report.blockers.map((item) => `- ${item}`).join("\n") : "- none"}
76
+
77
+ ## Warnings
78
+ ${report.warnings.length ? report.warnings.map((item) => `- ${item}`).join("\n") : "- none"}
79
+
80
+ ## Next Role Checklist
81
+ - [ ] Read the story and linked evidence file.
82
+ - [ ] Resolve blockers owned by the target role.
83
+ - [ ] Update durable artifacts before handing off again.
84
+ - [ ] Run \`speckit team audit ${report.story.path}\`.
85
+ `),
86
+ },
87
+ ]);
88
+ stdout.log(path);
89
+ return 0;
90
+ }
91
+ function renderReport(report, title) {
92
+ const colors = createColors();
93
+ if (!report.story)
94
+ return `${colors.bold(title)}: ${colors.red("blocked")}\n${report.blockers.join("\n")}`;
95
+ const lines = [
96
+ `${colors.bold(title)}: ${colors.status(report.status === "ok", "ok", "blocked")}`,
97
+ `Story: ${colors.cyan(report.story.path)}`,
98
+ `State: ${report.story.status ?? "missing"}`,
99
+ "",
100
+ ];
101
+ for (const role of report.roles) {
102
+ lines.push(`${colors.bold(role.role)}: ${colors.status(role.status === "ok", role.status, role.status)}`);
103
+ for (const check of role.checks) {
104
+ lines.push(` - ${colors.status(check.ok)} ${check.name}: ${check.detail}`);
105
+ }
106
+ lines.push("");
107
+ }
108
+ return lines.join("\n").trimEnd();
109
+ }
110
+ function renderAudit(report) {
111
+ const colors = createColors();
112
+ return [
113
+ `${colors.bold("Spec Team Audit")}: ${colors.status(report.status === "ok", "ok", "blocked")}`,
114
+ `Gate: ${report.gate}`,
115
+ report.story ? `Story: ${colors.cyan(report.story.path)}` : undefined,
116
+ "",
117
+ "Blocking:",
118
+ ...(report.blockers.length ? report.blockers.map((item) => `- ${item}`) : ["- none"]),
119
+ "",
120
+ "Warnings:",
121
+ ...(report.warnings.length ? report.warnings.map((item) => `- ${item}`) : ["- none"]),
122
+ ].filter((line) => line !== undefined).join("\n");
123
+ }
124
+ function requireTarget(options) {
125
+ if (options.target)
126
+ return options.target;
127
+ options.stdout?.error("Team command requires a story path or id.");
128
+ return undefined;
129
+ }