@trieungoctam/speckit 0.3.6 → 0.4.1
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 +28 -3
- package/dist/cli.js +19 -0
- package/dist/commands/plan.js +37 -0
- package/dist/commands/quick.js +38 -0
- package/dist/commands/score.d.ts +7 -0
- package/dist/commands/score.js +29 -0
- package/dist/commands/team.d.ts +10 -0
- package/dist/commands/team.js +129 -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/getting-started.md +131 -0
- package/docs/product-contract.md +21 -2
- package/docs/project-changelog.md +28 -0
- package/docs/release-checklist.md +5 -0
- package/docs/spec-quality-gates.md +27 -0
- package/docs/system-architecture.md +19 -1
- package/docs/team-workflow.md +62 -0
- package/docs/use-cases.md +40 -9
- package/docs/workflow-model.md +31 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,11 +10,18 @@ The curated skill set is intentionally small: `spec-shape`, `spec-research`, `sp
|
|
|
10
10
|
|
|
11
11
|
## Quickstart
|
|
12
12
|
|
|
13
|
+
Recommended enterprise setup:
|
|
14
|
+
|
|
13
15
|
```bash
|
|
14
16
|
npx @trieungoctam/speckit@latest setup
|
|
15
|
-
npx @trieungoctam/speckit@latest
|
|
16
|
-
npx @trieungoctam/speckit@latest
|
|
17
|
-
npx @trieungoctam/speckit@latest
|
|
17
|
+
npx @trieungoctam/speckit@latest doctor --deep
|
|
18
|
+
npx @trieungoctam/speckit@latest validate
|
|
19
|
+
npx @trieungoctam/speckit@latest score
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
One story end-to-end:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
18
25
|
npx @trieungoctam/speckit@latest memory refresh
|
|
19
26
|
npx @trieungoctam/speckit@latest session start "Add checkout validation"
|
|
20
27
|
npx @trieungoctam/speckit@latest quick "Add checkout validation"
|
|
@@ -23,6 +30,10 @@ npx @trieungoctam/speckit@latest sync
|
|
|
23
30
|
npx @trieungoctam/speckit@latest graph setup
|
|
24
31
|
npx @trieungoctam/speckit@latest sprint plan
|
|
25
32
|
npx @trieungoctam/speckit@latest graph triage --json
|
|
33
|
+
npx @trieungoctam/speckit@latest team status .speckit/stories/<story>.md
|
|
34
|
+
npx @trieungoctam/speckit@latest team audit .speckit/stories/<story>.md --json
|
|
35
|
+
npx @trieungoctam/speckit@latest team handoff .speckit/stories/<story>.md --from dev --to qa
|
|
36
|
+
npx @trieungoctam/speckit@latest score .speckit/stories/<story>.md
|
|
26
37
|
npx @trieungoctam/speckit@latest validate --json
|
|
27
38
|
npx @trieungoctam/speckit@latest permissions audit --path .env --json
|
|
28
39
|
npx @trieungoctam/speckit@latest ready .speckit/stories/<story>.md
|
|
@@ -30,6 +41,13 @@ npx @trieungoctam/speckit@latest session checkpoint --note "red complete"
|
|
|
30
41
|
npx @trieungoctam/speckit@latest review
|
|
31
42
|
```
|
|
32
43
|
|
|
44
|
+
Reset repo-local adapters before reinstalling:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
rm -rf .claude CLAUDE.md .codex AGENTS.md .cursor .opencode opencode.json
|
|
48
|
+
npx @trieungoctam/speckit@latest setup
|
|
49
|
+
```
|
|
50
|
+
|
|
33
51
|
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.
|
|
34
52
|
|
|
35
53
|
Or install globally:
|
|
@@ -85,6 +103,11 @@ For implementation stories, red-green-refactor evidence is mandatory. A story is
|
|
|
85
103
|
| `speckit session compact` | Create an anchored summary for resume or handoff. |
|
|
86
104
|
| `speckit session resume` | Mark a session as active and print its handoff path. |
|
|
87
105
|
| `speckit session status` | Print active session state paths. |
|
|
106
|
+
| `speckit team status <story>` | Show role-by-role team artifact readiness for a story. |
|
|
107
|
+
| `speckit team audit <story>` | Run enterprise review-gate checks and return nonzero when blockers remain. |
|
|
108
|
+
| `speckit team handoff <story>` | Create a durable role-to-role handoff artifact. |
|
|
109
|
+
| `speckit score` | Score project workflow health across contract, team model, context/session, and graph sync. |
|
|
110
|
+
| `speckit score <story>` | Score story health across team readiness, developer trace, TDD evidence, and review gate. |
|
|
88
111
|
| `speckit shape "<intent>"` | Create a short spec contract. |
|
|
89
112
|
| `speckit context <story>` | Build the current implementation context from a story. |
|
|
90
113
|
| `speckit quick "<intent>"` | Create one story plus matching TDD evidence file. |
|
|
@@ -117,7 +140,9 @@ See `docs/adapters.md` for exact output paths.
|
|
|
117
140
|
|
|
118
141
|
## Guides
|
|
119
142
|
|
|
143
|
+
- `docs/getting-started.md` covers setup, adapter reset, verification, story execution, team audit, and close.
|
|
120
144
|
- `docs/use-cases.md` covers setup, migration, quick changes, full planning, long sessions, TDD, graph automation, review, CI, and troubleshooting.
|
|
145
|
+
- `docs/team-workflow.md` covers enterprise role ownership, status, audit, and handoff flow.
|
|
121
146
|
- `docs/beads-viewer-setup.md` covers Beads Viewer installation, `bv` verification, and robot-safe graph usage.
|
|
122
147
|
- `docs/workflow-model.md` describes the quick and full workflow lanes.
|
|
123
148
|
- `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";
|
|
@@ -66,6 +68,21 @@ export async function main(argv = process.argv.slice(2), root = process.cwd()) {
|
|
|
66
68
|
note: value(parsed, "note"),
|
|
67
69
|
json: has(parsed, "json"),
|
|
68
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
|
+
});
|
|
69
86
|
case "sprint":
|
|
70
87
|
return sprintCommand({ root, action: requiredAction(parsed), json: has(parsed, "json") });
|
|
71
88
|
case "graph":
|
|
@@ -162,6 +179,8 @@ Usage:
|
|
|
162
179
|
speckit memory refresh
|
|
163
180
|
speckit permissions audit [--path <path>] [--command <command>] [--json]
|
|
164
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]
|
|
165
184
|
speckit sprint plan|next [--json]
|
|
166
185
|
speckit graph setup|triage|plan|insights [--json]
|
|
167
186
|
speckit validate [--json]
|
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
|
]);
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/core/scaffold.js
CHANGED
|
@@ -2,6 +2,7 @@ import { markdown, text } from "./managed-files.js";
|
|
|
2
2
|
import { agilePolicy, enterpriseSafetyPolicy, tddPolicy, workflowReview, workflowShape, workflowTddRun, } from "./policy.js";
|
|
3
3
|
import { permissionPolicyFile } from "./permission-policy.js";
|
|
4
4
|
import { storyTemplate, tddEvidenceTemplate } from "./templates.js";
|
|
5
|
+
import { teamFiles } from "./team-scaffold.js";
|
|
5
6
|
export function coreFiles() {
|
|
6
7
|
return [
|
|
7
8
|
{
|
|
@@ -48,6 +49,7 @@ adapters:
|
|
|
48
49
|
}
|
|
49
50
|
export function enterpriseFiles() {
|
|
50
51
|
return [
|
|
52
|
+
...teamFiles(),
|
|
51
53
|
{
|
|
52
54
|
path: ".speckit/flows/spec-flow.md",
|
|
53
55
|
content: markdown(`# Spec Flow
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ScoreCategory = {
|
|
2
|
+
name: string;
|
|
3
|
+
score: number;
|
|
4
|
+
passed: number;
|
|
5
|
+
total: number;
|
|
6
|
+
detail: string;
|
|
7
|
+
};
|
|
8
|
+
export type SpecScoreReport = {
|
|
9
|
+
score: number;
|
|
10
|
+
target?: string;
|
|
11
|
+
status: "excellent" | "healthy" | "needs-attention" | "blocked";
|
|
12
|
+
categories: ScoreCategory[];
|
|
13
|
+
blockers: string[];
|
|
14
|
+
recommendations: string[];
|
|
15
|
+
};
|
|
16
|
+
export declare function calculateSpecScore(root: string, target?: string): Promise<SpecScoreReport>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { buildTeamReport } from "./team-report.js";
|
|
4
|
+
import { validateWorkflowContract } from "./workflow-validator.js";
|
|
5
|
+
export async function calculateSpecScore(root, target) {
|
|
6
|
+
const categories = target
|
|
7
|
+
? await storyCategories(root, target)
|
|
8
|
+
: await projectCategories(root);
|
|
9
|
+
const score = Math.round(categories.reduce((sum, item) => sum + item.score, 0) / categories.length);
|
|
10
|
+
const blockers = categories
|
|
11
|
+
.filter((item) => item.score < 70)
|
|
12
|
+
.map((item) => `${item.name}: ${item.detail}`);
|
|
13
|
+
return {
|
|
14
|
+
score,
|
|
15
|
+
target,
|
|
16
|
+
status: statusFor(score, blockers.length),
|
|
17
|
+
categories,
|
|
18
|
+
blockers,
|
|
19
|
+
recommendations: recommendationsFor(categories, Boolean(target)),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function projectCategories(root) {
|
|
23
|
+
const contract = await validateWorkflowContract(root);
|
|
24
|
+
return [
|
|
25
|
+
ratio("Workflow Contract", contract.filter((check) => check.ok).length, contract.length, "Core prompts, skills, adapters, and permissions"),
|
|
26
|
+
await fileScore(root, "Team Operating Model", [
|
|
27
|
+
".speckit/team/roles.md",
|
|
28
|
+
".speckit/team/artifact-ownership.md",
|
|
29
|
+
".speckit/team/working-agreement.md",
|
|
30
|
+
".speckit/team/review-gates.md",
|
|
31
|
+
".speckit/team/handoff-protocol.md",
|
|
32
|
+
]),
|
|
33
|
+
await fileScore(root, "Context And Session", [
|
|
34
|
+
".speckit/memory/project-context.md",
|
|
35
|
+
".speckit/sessions/active.md",
|
|
36
|
+
".speckit/context/current.md",
|
|
37
|
+
".speckit/context/subagent-handoff.md",
|
|
38
|
+
]),
|
|
39
|
+
await fileScore(root, "Graph Sync", [
|
|
40
|
+
".speckit/sync/beads-sync.jsonl",
|
|
41
|
+
".beads/beads.jsonl",
|
|
42
|
+
]),
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
async function storyCategories(root, target) {
|
|
46
|
+
const team = await buildTeamReport(root, target);
|
|
47
|
+
if (!team.story) {
|
|
48
|
+
return [ratio("Story", 0, 1, team.blockers[0] ?? "Story not found")];
|
|
49
|
+
}
|
|
50
|
+
const allRoleChecks = team.roles.flatMap((role) => role.checks);
|
|
51
|
+
const qa = team.roles.find((role) => role.role === "QA/Test");
|
|
52
|
+
const developer = team.roles.find((role) => role.role === "Developer");
|
|
53
|
+
const reviewer = team.roles.find((role) => role.role === "Reviewer/Lead");
|
|
54
|
+
return [
|
|
55
|
+
ratio("Team Readiness", allRoleChecks.filter((check) => check.ok).length, allRoleChecks.length, "Role-owned story artifacts"),
|
|
56
|
+
ratio("Developer Trace", developer?.checks.filter((check) => check.ok).length ?? 0, developer?.checks.length ?? 1, "Dev Agent Record, File List, Change Log"),
|
|
57
|
+
ratio("TDD Evidence", qa?.checks.filter((check) => check.ok).length ?? 0, qa?.checks.length ?? 1, "Red, green, and refactor evidence"),
|
|
58
|
+
ratio("Review Gate", reviewer?.checks.filter((check) => check.ok).length ?? 0, reviewer?.checks.length ?? 1, "Review evidence and follow-ups"),
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
async function fileScore(root, name, paths) {
|
|
62
|
+
const checks = await Promise.all(paths.map((path) => exists(root, path)));
|
|
63
|
+
const passed = checks.filter(Boolean).length;
|
|
64
|
+
return ratio(name, passed, paths.length, `${passed}/${paths.length} files present`);
|
|
65
|
+
}
|
|
66
|
+
function ratio(name, passed, total, detail) {
|
|
67
|
+
const denominator = Math.max(total, 1);
|
|
68
|
+
return {
|
|
69
|
+
name,
|
|
70
|
+
score: Math.round((passed / denominator) * 100),
|
|
71
|
+
passed,
|
|
72
|
+
total,
|
|
73
|
+
detail,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function statusFor(score, blockers) {
|
|
77
|
+
if (score >= 90 && blockers === 0)
|
|
78
|
+
return "excellent";
|
|
79
|
+
if (score >= 75)
|
|
80
|
+
return "healthy";
|
|
81
|
+
if (score >= 50)
|
|
82
|
+
return "needs-attention";
|
|
83
|
+
return "blocked";
|
|
84
|
+
}
|
|
85
|
+
function recommendationsFor(categories, storyMode) {
|
|
86
|
+
const weak = categories.filter((item) => item.score < 100);
|
|
87
|
+
if (weak.length === 0)
|
|
88
|
+
return ["Workflow is fully aligned."];
|
|
89
|
+
return weak.map((item) => {
|
|
90
|
+
if (storyMode && item.name === "TDD Evidence")
|
|
91
|
+
return "Complete red, green, and refactor evidence before review.";
|
|
92
|
+
if (storyMode && item.name === "Developer Trace")
|
|
93
|
+
return "Update Dev Agent Record, File List, and Change Log.";
|
|
94
|
+
if (storyMode && item.name === "Review Gate")
|
|
95
|
+
return "Record review outcome and follow-ups.";
|
|
96
|
+
if (item.name === "Context And Session")
|
|
97
|
+
return "Run memory refresh, session start, context, and compact before long work.";
|
|
98
|
+
if (item.name === "Graph Sync")
|
|
99
|
+
return "Run speckit sync and speckit graph setup.";
|
|
100
|
+
return `Fix ${item.name}.`;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async function exists(root, path) {
|
|
104
|
+
try {
|
|
105
|
+
await access(join(root, path));
|
|
106
|
+
const content = await readFile(join(root, path), "utf8");
|
|
107
|
+
return content.trim().length > 0;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { StoryResolution } from "./story.js";
|
|
2
|
+
export type TeamCheck = {
|
|
3
|
+
name: string;
|
|
4
|
+
ok: boolean;
|
|
5
|
+
detail: string;
|
|
6
|
+
};
|
|
7
|
+
export type TeamRoleReport = {
|
|
8
|
+
role: string;
|
|
9
|
+
status: "ok" | "blocked" | "needs-attention" | "waiting";
|
|
10
|
+
checks: TeamCheck[];
|
|
11
|
+
};
|
|
12
|
+
export type TeamReport = {
|
|
13
|
+
status: "ok" | "blocked";
|
|
14
|
+
story?: Pick<StoryResolution, "id" | "path" | "title" | "status" | "evidencePath">;
|
|
15
|
+
roles: TeamRoleReport[];
|
|
16
|
+
blockers: string[];
|
|
17
|
+
warnings: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare function buildTeamReport(root: string, target: string): Promise<TeamReport>;
|