@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.
- package/README.md +12 -0
- package/dist/cli.js +22 -1
- package/dist/commands/doctor.js +12 -9
- package/dist/commands/graph.js +4 -2
- package/dist/commands/init.js +4 -2
- package/dist/commands/plan.js +37 -0
- package/dist/commands/quick.js +38 -0
- package/dist/commands/ready.js +6 -4
- package/dist/commands/score.d.ts +7 -0
- package/dist/commands/score.js +29 -0
- package/dist/commands/setup.js +24 -21
- package/dist/commands/sync.js +4 -2
- package/dist/commands/team.d.ts +10 -0
- package/dist/commands/team.js +129 -0
- package/dist/commands/validate.js +4 -2
- package/dist/core/colors.d.ts +11 -0
- package/dist/core/colors.js +40 -0
- package/dist/core/scaffold.js +2 -0
- package/dist/core/spec-score.d.ts +16 -0
- package/dist/core/spec-score.js +112 -0
- package/dist/core/team-report.d.ts +19 -0
- package/dist/core/team-report.js +149 -0
- package/dist/core/team-scaffold.d.ts +2 -0
- package/dist/core/team-scaffold.js +92 -0
- package/dist/core/templates.d.ts +1 -1
- package/dist/core/templates.js +15 -0
- package/docs/adapters.md +12 -0
- package/docs/code-standards.md +2 -0
- package/docs/development-roadmap.md +7 -3
- package/docs/enterprise-rollout.md +8 -3
- package/docs/product-contract.md +21 -2
- package/docs/project-changelog.md +31 -0
- package/docs/release-checklist.md +4 -0
- package/docs/spec-quality-gates.md +27 -0
- package/docs/system-architecture.md +19 -1
- package/docs/team-workflow.md +40 -0
- package/docs/use-cases.md +11 -0
- package/docs/workflow-model.md +31 -0
- 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
|
-
|
|
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]
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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(
|
|
33
|
-
stdout.log(
|
|
34
|
-
stdout.log(`Tools: ${tools.map((tool) => `${tool.name}=${tool.available
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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;
|
package/dist/commands/graph.js
CHANGED
|
@@ -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(
|
|
56
|
+
stdout.log(`${colors.bold("Mirror")}: ${colors.cyan(mirrorPath)}`);
|
|
55
57
|
for (const tool of tools) {
|
|
56
|
-
stdout.log(`${tool.name}: ${tool.available
|
|
58
|
+
stdout.log(`${colors.bold(tool.name)}: ${colors.status(tool.available)} - ${colors.dim(tool.hint)}`);
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
return 0;
|
package/dist/commands/init.js
CHANGED
|
@@ -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(
|
|
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(
|
|
20
|
+
stdout.log(`${colors.yellow("skipped")} ${result.path}: ${result.reason}`);
|
|
19
21
|
}
|
|
20
22
|
return skipped.length > 0 ? 2 : 0;
|
|
21
23
|
}
|
package/dist/commands/plan.js
CHANGED
|
@@ -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
|
];
|
package/dist/commands/quick.js
CHANGED
|
@@ -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
|
]);
|
package/dist/commands/ready.js
CHANGED
|
@@ -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(
|
|
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
|
|
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,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
|
+
}
|
package/dist/commands/setup.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
}
|
package/dist/commands/sync.js
CHANGED
|
@@ -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
|
+
}
|