@trieungoctam/speckit 0.1.0 → 0.2.2
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 +16 -1
- package/dist/adapters/antigravity-adapter.js +5 -5
- package/dist/adapters/claude-code-adapter.js +8 -5
- package/dist/adapters/codex-adapter.js +5 -3
- package/dist/adapters/cursor-adapter.js +6 -3
- package/dist/adapters/opencode-adapter.js +1 -0
- package/dist/adapters/tool-checks.js +0 -1
- package/dist/cli.js +24 -2
- package/dist/commands/close.d.ts +6 -0
- package/dist/commands/close.js +34 -0
- package/dist/commands/context.d.ts +6 -0
- package/dist/commands/context.js +87 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +29 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +5 -3
- package/dist/commands/plan.js +19 -2
- package/dist/commands/quick.js +20 -2
- package/dist/commands/ready.d.ts +7 -0
- package/dist/commands/ready.js +20 -0
- package/dist/commands/run.js +19 -24
- package/dist/commands/start.d.ts +6 -0
- package/dist/commands/start.js +47 -0
- package/dist/commands/sync.js +2 -8
- package/dist/commands/triage.d.ts +6 -0
- package/dist/commands/triage.js +33 -0
- package/dist/core/readiness.d.ts +13 -0
- package/dist/core/readiness.js +120 -0
- package/dist/core/scaffold.d.ts +1 -0
- package/dist/core/scaffold.js +107 -2
- package/dist/core/story.d.ts +13 -0
- package/dist/core/story.js +67 -0
- package/docs/development-roadmap.md +8 -7
- package/docs/product-contract.md +5 -5
- package/docs/project-changelog.md +67 -1
- package/docs/release-checklist.md +2 -2
- package/docs/spec-enterprise-harness-plan.md +68 -0
- package/docs/spec-quality-gates.md +70 -0
- package/docs/system-architecture.md +1 -1
- package/package.json +1 -1
package/dist/commands/run.js
CHANGED
|
@@ -1,42 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { resolveStory } from "../core/story.js";
|
|
2
|
+
import { evaluateReadiness } from "../core/readiness.js";
|
|
3
3
|
export async function runCommand(options) {
|
|
4
4
|
const stdout = options.stdout ?? console;
|
|
5
|
-
const
|
|
6
|
-
if (!
|
|
5
|
+
const story = await resolveStory(options.root, options.target);
|
|
6
|
+
if (!story) {
|
|
7
7
|
stdout.error(`Story not found: ${options.target}`);
|
|
8
8
|
return 1;
|
|
9
9
|
}
|
|
10
|
+
const readiness = await evaluateReadiness(options.root, options.target);
|
|
11
|
+
if (readiness.status !== "ready") {
|
|
12
|
+
stdout.error(`Story is not ready for implementation: ${story.path}`);
|
|
13
|
+
for (const check of readiness.checks.filter((item) => !item.ok)) {
|
|
14
|
+
stdout.error(`- ${check.name}: ${check.detail}`);
|
|
15
|
+
}
|
|
16
|
+
stdout.error(`Run \`speckit ready ${story.id}\` after preparing context and sync.`);
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
10
19
|
stdout.log(`# Speckit TDD Run
|
|
11
20
|
|
|
12
|
-
Story: ${
|
|
21
|
+
Story: ${story.path}
|
|
22
|
+
|
|
23
|
+
Status: ready-for-dev
|
|
13
24
|
|
|
14
|
-
1. Read
|
|
25
|
+
1. Read \`.speckit/context/current.md\`, story, acceptance criteria, and subagent handoff.
|
|
15
26
|
2. Identify the smallest executable test that should fail.
|
|
16
|
-
3. Record red evidence in the
|
|
27
|
+
3. Record red evidence in the linked TDD evidence file.
|
|
17
28
|
4. Implement the minimal behavior.
|
|
18
29
|
5. Record green evidence.
|
|
19
30
|
6. Refactor only after tests are green, then record validation.
|
|
31
|
+
7. Continue until all acceptance criteria are satisfied or a stop condition is hit.
|
|
20
32
|
|
|
21
33
|
Recommended handoff:
|
|
22
34
|
\`speckit review\` before commit or PR.
|
|
23
35
|
`);
|
|
24
36
|
return 0;
|
|
25
37
|
}
|
|
26
|
-
async function resolveStory(root, target) {
|
|
27
|
-
const candidates = [
|
|
28
|
-
target,
|
|
29
|
-
`.speckit/stories/${target}`,
|
|
30
|
-
`.speckit/stories/${target}.md`,
|
|
31
|
-
];
|
|
32
|
-
for (const candidate of candidates) {
|
|
33
|
-
try {
|
|
34
|
-
await access(join(root, candidate));
|
|
35
|
-
return candidate;
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// Try the next candidate.
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { markdown, writeManagedFiles } from "../core/managed-files.js";
|
|
2
|
+
import { slugify, timestamp } from "../core/slug.js";
|
|
3
|
+
export async function startCommand(options) {
|
|
4
|
+
const stdout = options.stdout ?? console;
|
|
5
|
+
const sessionId = `${timestamp()}-${slugify(options.intent)}`;
|
|
6
|
+
const handoffPath = `.speckit/sessions/${sessionId}/handoff.md`;
|
|
7
|
+
const contextPath = ".speckit/context/current.md";
|
|
8
|
+
await writeManagedFiles(options.root, [
|
|
9
|
+
{
|
|
10
|
+
path: handoffPath,
|
|
11
|
+
content: markdown(`# Spec Session: ${sessionId}
|
|
12
|
+
|
|
13
|
+
## Idea
|
|
14
|
+
${options.intent}
|
|
15
|
+
|
|
16
|
+
## Current Phase
|
|
17
|
+
shape
|
|
18
|
+
|
|
19
|
+
## Linked Artifacts
|
|
20
|
+
- Context: \`${contextPath}\`
|
|
21
|
+
|
|
22
|
+
## Next Command
|
|
23
|
+
\`speckit shape "${options.intent}"\`
|
|
24
|
+
`),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
path: contextPath,
|
|
28
|
+
content: markdown(`# Current Spec Context
|
|
29
|
+
|
|
30
|
+
## Session
|
|
31
|
+
${sessionId}
|
|
32
|
+
|
|
33
|
+
## Idea
|
|
34
|
+
${options.intent}
|
|
35
|
+
|
|
36
|
+
## Active Artifact
|
|
37
|
+
\`${handoffPath}\`
|
|
38
|
+
|
|
39
|
+
## Next Command
|
|
40
|
+
\`speckit shape "${options.intent}"\`
|
|
41
|
+
`),
|
|
42
|
+
},
|
|
43
|
+
], true);
|
|
44
|
+
stdout.log(sessionId);
|
|
45
|
+
stdout.log(handoffPath);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
package/dist/commands/sync.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { markdown, writeManagedFiles } from "../core/managed-files.js";
|
|
4
|
+
import { firstHeading, frontmatterValue } from "../core/story.js";
|
|
4
5
|
export async function syncCommand(options) {
|
|
5
6
|
const stdout = options.stdout ?? console;
|
|
6
7
|
const storiesDir = join(options.root, ".speckit", "stories");
|
|
@@ -19,7 +20,7 @@ export async function syncCommand(options) {
|
|
|
19
20
|
id: entry.replace(/\.md$/, ""),
|
|
20
21
|
path: `.speckit/stories/${entry}`,
|
|
21
22
|
title: firstHeading(content) ?? entry.replace(/\.md$/, ""),
|
|
22
|
-
status: content.includes("- [ ]") ? "open" : "ready-for-review",
|
|
23
|
+
status: frontmatterValue(content, "status") ?? (content.includes("- [ ]") ? "open" : "ready-for-review"),
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
const jsonl = stories.map((story) => JSON.stringify(story)).join("\n");
|
|
@@ -45,10 +46,3 @@ bv --robot-next --format json
|
|
|
45
46
|
stdout.log(`Synced ${stories.length} stories to .speckit/sync/beads-sync.jsonl`);
|
|
46
47
|
return 0;
|
|
47
48
|
}
|
|
48
|
-
function firstHeading(content) {
|
|
49
|
-
return content
|
|
50
|
-
.split("\n")
|
|
51
|
-
.find((line) => line.startsWith("# "))
|
|
52
|
-
?.replace(/^#\s+/, "")
|
|
53
|
-
.trim();
|
|
54
|
-
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export async function triageCommand(options) {
|
|
4
|
+
const stdout = options.stdout ?? console;
|
|
5
|
+
const stories = await readSyncedStories(options.root);
|
|
6
|
+
const open = stories.filter((story) => ["open", "ready-for-dev", "in-progress"].includes(story.status));
|
|
7
|
+
const ready = stories.filter((story) => story.status !== "open");
|
|
8
|
+
const report = {
|
|
9
|
+
source: ".speckit/sync/beads-sync.jsonl",
|
|
10
|
+
open: open.length,
|
|
11
|
+
ready: ready.length,
|
|
12
|
+
next: open[0] ?? null,
|
|
13
|
+
};
|
|
14
|
+
if (options.json) {
|
|
15
|
+
stdout.log(JSON.stringify(report, null, 2));
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
stdout.log(`Spec triage: ${open.length} open, ${ready.length} ready`);
|
|
19
|
+
stdout.log(open[0] ? `Next: ${open[0].id} (${open[0].path})` : "Next: none");
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
async function readSyncedStories(root) {
|
|
23
|
+
try {
|
|
24
|
+
const content = await readFile(join(root, ".speckit", "sync", "beads-sync.jsonl"), "utf8");
|
|
25
|
+
return content
|
|
26
|
+
.split("\n")
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.map((line) => JSON.parse(line));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { StoryResolution } from "./story.js";
|
|
2
|
+
export type ReadinessCheck = {
|
|
3
|
+
name: string;
|
|
4
|
+
ok: boolean;
|
|
5
|
+
detail: string;
|
|
6
|
+
};
|
|
7
|
+
export type ReadinessReport = {
|
|
8
|
+
story?: Pick<StoryResolution, "id" | "path" | "title" | "status" | "evidencePath">;
|
|
9
|
+
checks: ReadinessCheck[];
|
|
10
|
+
status: "ready" | "blocked";
|
|
11
|
+
};
|
|
12
|
+
export declare function evaluateReadiness(root: string, target: string): Promise<ReadinessReport>;
|
|
13
|
+
export declare function evidenceStatus(root: string, evidencePath: string | undefined): Promise<string | undefined>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { extractSection, frontmatterValue, resolveStory } from "./story.js";
|
|
4
|
+
export async function evaluateReadiness(root, target) {
|
|
5
|
+
const story = await resolveStory(root, target);
|
|
6
|
+
if (!story) {
|
|
7
|
+
return {
|
|
8
|
+
checks: [{ name: "story", ok: false, detail: `Story not found: ${target}` }],
|
|
9
|
+
status: "blocked",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const evidencePath = story.evidencePath;
|
|
13
|
+
const checks = [
|
|
14
|
+
{
|
|
15
|
+
name: "story-status",
|
|
16
|
+
ok: story.status === "ready-for-dev",
|
|
17
|
+
detail: story.status ? `status=${story.status}` : "status missing",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "acceptance-criteria",
|
|
21
|
+
ok: hasActionableAcceptanceCriteria(story.content),
|
|
22
|
+
detail: "story has testable acceptance criteria",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "evidence-link",
|
|
26
|
+
ok: Boolean(evidencePath),
|
|
27
|
+
detail: evidencePath ?? "missing TDD evidence reference",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "evidence-file",
|
|
31
|
+
ok: evidencePath ? await fileExists(root, evidencePath) : false,
|
|
32
|
+
detail: evidencePath ?? "missing TDD evidence reference",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "context",
|
|
36
|
+
ok: await contextIsFreshForStory(root, story.path),
|
|
37
|
+
detail: ".speckit/context/current.md links this story and is fresh",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "graph-sync",
|
|
41
|
+
ok: await graphContainsStory(root, story.path),
|
|
42
|
+
detail: ".speckit/sync/beads-sync.jsonl contains this story",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "tool-policy",
|
|
46
|
+
ok: await fileExists(root, ".speckit/tool-policy.yaml"),
|
|
47
|
+
detail: ".speckit/tool-policy.yaml",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "safety-policy",
|
|
51
|
+
ok: await fileExists(root, ".speckit/rules/enterprise-safety.md"),
|
|
52
|
+
detail: ".speckit/rules/enterprise-safety.md",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
return {
|
|
56
|
+
story: {
|
|
57
|
+
id: story.id,
|
|
58
|
+
path: story.path,
|
|
59
|
+
title: story.title,
|
|
60
|
+
status: story.status,
|
|
61
|
+
evidencePath,
|
|
62
|
+
},
|
|
63
|
+
checks,
|
|
64
|
+
status: checks.every((check) => check.ok) ? "ready" : "blocked",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function hasActionableAcceptanceCriteria(content) {
|
|
68
|
+
const criteria = extractSection(content, "Acceptance Criteria");
|
|
69
|
+
if (!criteria)
|
|
70
|
+
return false;
|
|
71
|
+
return /Given .+|When .+|Then .+|\d+\./i.test(criteria) && !criteria.includes("...");
|
|
72
|
+
}
|
|
73
|
+
async function contextIsFreshForStory(root, storyPath) {
|
|
74
|
+
try {
|
|
75
|
+
const content = await readFile(join(root, ".speckit", "context", "current.md"), "utf8");
|
|
76
|
+
return content.includes("status: fresh") && content.includes(storyPath);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function graphContainsStory(root, storyPath) {
|
|
83
|
+
try {
|
|
84
|
+
const content = await readFile(join(root, ".speckit", "sync", "beads-sync.jsonl"), "utf8");
|
|
85
|
+
return content
|
|
86
|
+
.split("\n")
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.some((line) => {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(line).path === storyPath;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function fileExists(root, path) {
|
|
102
|
+
try {
|
|
103
|
+
await access(join(root, path));
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export async function evidenceStatus(root, evidencePath) {
|
|
111
|
+
if (!evidencePath)
|
|
112
|
+
return undefined;
|
|
113
|
+
try {
|
|
114
|
+
const content = await readFile(join(root, evidencePath), "utf8");
|
|
115
|
+
return frontmatterValue(content, "status");
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
package/dist/core/scaffold.d.ts
CHANGED
package/dist/core/scaffold.js
CHANGED
|
@@ -27,7 +27,13 @@ adapters:
|
|
|
27
27
|
{ path: ".speckit/workflows/review.md", content: markdown(workflowReview) },
|
|
28
28
|
{
|
|
29
29
|
path: ".speckit/templates/story.md",
|
|
30
|
-
content: markdown(
|
|
30
|
+
content: markdown(`---
|
|
31
|
+
status: draft
|
|
32
|
+
evidence: .speckit/evidence/{{slug}}-tdd-evidence.md
|
|
33
|
+
context: pending
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
# Story: {{title}}
|
|
31
37
|
|
|
32
38
|
## Intent
|
|
33
39
|
{{intent}}
|
|
@@ -46,11 +52,23 @@ adapters:
|
|
|
46
52
|
## Notes
|
|
47
53
|
- Risks:
|
|
48
54
|
- Dependencies:
|
|
55
|
+
|
|
56
|
+
## Spec Anti-Mistake Checklist
|
|
57
|
+
- Reuse existing project patterns before adding new files.
|
|
58
|
+
- Verify file locations before editing.
|
|
59
|
+
- Do not introduce new libraries without explicit need.
|
|
60
|
+
- Preserve existing behavior unless an acceptance criterion requires change.
|
|
61
|
+
- Capture previous-story learnings if this continues prior work.
|
|
49
62
|
`),
|
|
50
63
|
},
|
|
51
64
|
{
|
|
52
65
|
path: ".speckit/templates/tdd-evidence.md",
|
|
53
|
-
content: markdown(
|
|
66
|
+
content: markdown(`---
|
|
67
|
+
status: missing
|
|
68
|
+
story: {{story}}
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
# TDD Evidence: {{story}}
|
|
54
72
|
|
|
55
73
|
## Test Intent
|
|
56
74
|
|
|
@@ -69,3 +87,90 @@ adapters:
|
|
|
69
87
|
},
|
|
70
88
|
];
|
|
71
89
|
}
|
|
90
|
+
export function enterpriseFiles() {
|
|
91
|
+
return [
|
|
92
|
+
{
|
|
93
|
+
path: ".speckit/flows/spec-flow.md",
|
|
94
|
+
content: markdown(`# Spec Flow
|
|
95
|
+
|
|
96
|
+
## Order
|
|
97
|
+
start -> shape -> plan -> context -> sync -> triage -> run -> review -> close
|
|
98
|
+
|
|
99
|
+
## Traceability
|
|
100
|
+
- A session links the original idea to every downstream artifact.
|
|
101
|
+
- A story links acceptance criteria to TDD evidence.
|
|
102
|
+
- Sync links stories to graph-ready JSONL.
|
|
103
|
+
- Close links review output back to story and graph sync.
|
|
104
|
+
`),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
path: ".speckit/tool-policy.yaml",
|
|
108
|
+
content: text(`version: 1
|
|
109
|
+
phases:
|
|
110
|
+
shape:
|
|
111
|
+
may_edit_code: false
|
|
112
|
+
plan:
|
|
113
|
+
may_edit_code: false
|
|
114
|
+
run:
|
|
115
|
+
may_edit_code: true
|
|
116
|
+
requires_tdd_evidence: true
|
|
117
|
+
review:
|
|
118
|
+
may_edit_code: false
|
|
119
|
+
graph:
|
|
120
|
+
robot_only: true`),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
path: ".speckit/prompts/spec-run.md",
|
|
124
|
+
content: markdown(`# Spec Run Prompt
|
|
125
|
+
|
|
126
|
+
## Required Inputs
|
|
127
|
+
|
|
128
|
+
- Current context: \`.speckit/context/current.md\`
|
|
129
|
+
- Subagent handoff: \`.speckit/context/subagent-handoff.md\`
|
|
130
|
+
- Story with acceptance criteria
|
|
131
|
+
- Matching TDD evidence file
|
|
132
|
+
- Tool policy: \`.speckit/tool-policy.yaml\`
|
|
133
|
+
|
|
134
|
+
## Status Preconditions
|
|
135
|
+
|
|
136
|
+
- Story status is \`ready-for-dev\`.
|
|
137
|
+
- Context status is \`fresh\`.
|
|
138
|
+
- \`speckit ready <story>\` returns ready.
|
|
139
|
+
|
|
140
|
+
## Allowed Edits
|
|
141
|
+
|
|
142
|
+
- Implementation files needed for the story.
|
|
143
|
+
- Matching tests.
|
|
144
|
+
- Linked TDD evidence file.
|
|
145
|
+
- Story completion checkboxes and implementation notes.
|
|
146
|
+
|
|
147
|
+
## Execution Contract
|
|
148
|
+
|
|
149
|
+
Use the red-green-refactor loop.
|
|
150
|
+
|
|
151
|
+
1. Read the current context before touching code.
|
|
152
|
+
2. Confirm the story path, acceptance criteria, and evidence path.
|
|
153
|
+
3. Write or run the smallest failing test first.
|
|
154
|
+
4. Record red evidence before implementation.
|
|
155
|
+
5. Implement the smallest change that satisfies the failing test.
|
|
156
|
+
6. Record green evidence after tests pass.
|
|
157
|
+
7. Refactor only while tests stay green, then record validation.
|
|
158
|
+
8. Use graph commands only through robot-safe flags such as \`bv --robot-next --format json\`.
|
|
159
|
+
9. Run \`speckit review\` before handoff.
|
|
160
|
+
|
|
161
|
+
## Stop Conditions
|
|
162
|
+
|
|
163
|
+
- Stop if acceptance criteria are missing.
|
|
164
|
+
- Stop if the evidence file cannot be identified.
|
|
165
|
+
- Stop if \`speckit ready <story>\` reports blocked.
|
|
166
|
+
- Stop before destructive commands, production changes, or secret access.
|
|
167
|
+
|
|
168
|
+
## Completion Signal
|
|
169
|
+
|
|
170
|
+
- All acceptance criteria satisfied.
|
|
171
|
+
- TDD evidence status can be advanced through red, green, and refactor.
|
|
172
|
+
- Review handoff is ready.
|
|
173
|
+
`),
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type StoryResolution = {
|
|
2
|
+
path: string;
|
|
3
|
+
content: string;
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
evidencePath?: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function resolveStory(root: string, target: string): Promise<StoryResolution | undefined>;
|
|
10
|
+
export declare function frontmatterValue(content: string, key: string): string | undefined;
|
|
11
|
+
export declare function extractEvidenceReference(content: string): string | undefined;
|
|
12
|
+
export declare function extractSection(content: string, heading: string): string | undefined;
|
|
13
|
+
export declare function firstHeading(content: string): string | undefined;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
export async function resolveStory(root, target) {
|
|
4
|
+
const candidates = [
|
|
5
|
+
target,
|
|
6
|
+
`.speckit/stories/${target}`,
|
|
7
|
+
`.speckit/stories/${target}.md`,
|
|
8
|
+
];
|
|
9
|
+
for (const candidate of candidates) {
|
|
10
|
+
try {
|
|
11
|
+
const fullPath = join(root, candidate);
|
|
12
|
+
await access(fullPath);
|
|
13
|
+
const content = await readFile(fullPath, "utf8");
|
|
14
|
+
return {
|
|
15
|
+
path: candidate,
|
|
16
|
+
content,
|
|
17
|
+
id: basename(candidate).replace(/\.md$/, ""),
|
|
18
|
+
title: firstHeading(content) ?? basename(candidate).replace(/\.md$/, ""),
|
|
19
|
+
status: frontmatterValue(content, "status"),
|
|
20
|
+
evidencePath: frontmatterValue(content, "evidence") ?? extractEvidenceReference(content),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Try the next candidate.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
export function frontmatterValue(content, key) {
|
|
30
|
+
const lines = content.split("\n");
|
|
31
|
+
const start = lines.findIndex((line) => line.trim() === "---");
|
|
32
|
+
if (start === -1)
|
|
33
|
+
return undefined;
|
|
34
|
+
const end = lines.findIndex((line, index) => index > start && line.trim() === "---");
|
|
35
|
+
if (end === -1)
|
|
36
|
+
return undefined;
|
|
37
|
+
const prefix = `${key}:`;
|
|
38
|
+
const found = lines
|
|
39
|
+
.slice(start + 1, end)
|
|
40
|
+
.find((line) => line.trim().toLowerCase().startsWith(prefix.toLowerCase()));
|
|
41
|
+
return found?.slice(found.indexOf(":") + 1).trim().replace(/^["']|["']$/g, "");
|
|
42
|
+
}
|
|
43
|
+
export function extractEvidenceReference(content) {
|
|
44
|
+
const match = content.match(/`([^`]*tdd-evidence\.md)`/i);
|
|
45
|
+
return match?.[1];
|
|
46
|
+
}
|
|
47
|
+
export function extractSection(content, heading) {
|
|
48
|
+
const lines = content.split("\n");
|
|
49
|
+
const start = lines.findIndex((line) => line.trim().toLowerCase() === `## ${heading}`.toLowerCase());
|
|
50
|
+
if (start === -1)
|
|
51
|
+
return undefined;
|
|
52
|
+
const collected = [];
|
|
53
|
+
for (const line of lines.slice(start + 1)) {
|
|
54
|
+
if (line.startsWith("## "))
|
|
55
|
+
break;
|
|
56
|
+
if (line.trim())
|
|
57
|
+
collected.push(line);
|
|
58
|
+
}
|
|
59
|
+
return collected.length > 0 ? collected.join("\n") : undefined;
|
|
60
|
+
}
|
|
61
|
+
export function firstHeading(content) {
|
|
62
|
+
return content
|
|
63
|
+
.split("\n")
|
|
64
|
+
.find((line) => line.startsWith("# "))
|
|
65
|
+
?.replace(/^#\s+/, "")
|
|
66
|
+
.trim();
|
|
67
|
+
}
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
Speckit MVP is implemented and pushed to `git@github.com:trieungoctam/speckit.git` on `main`.
|
|
6
6
|
|
|
7
|
-
Current package target: `@trieungoctam/speckit@0.
|
|
7
|
+
Current package target: `@trieungoctam/speckit@0.2.2`.
|
|
8
8
|
|
|
9
|
-
The CLI is npx-ready, generates Agile + TDD rules, creates workflow artifacts, supports five IDE adapters, wraps Beads Viewer safely, and has automated tests plus CI.
|
|
9
|
+
The CLI is npx-ready, generates Agile + TDD rules, creates workflow artifacts, supports five IDE adapters, wraps Beads Viewer safely, includes an enterprise harness, and has automated prompt/readiness tests plus CI.
|
|
10
10
|
|
|
11
11
|
## Milestones
|
|
12
12
|
|
|
@@ -14,17 +14,18 @@ The CLI is npx-ready, generates Agile + TDD rules, creates workflow artifacts, s
|
|
|
14
14
|
| --- | --- | --- |
|
|
15
15
|
| Product contract | Complete | Contract, workflow model, and command surface documented. |
|
|
16
16
|
| CLI scaffold | Complete | `bin/speckit`, TypeScript build, command router, managed file writer. |
|
|
17
|
-
| Workflow engine |
|
|
17
|
+
| Workflow engine | Complete | `start`, `shape`, `plan`, `context`, `quick`, `sync`, `triage`, `ready`, `run`, `review`, and `close` generate linked artifacts. |
|
|
18
18
|
| IDE adapters | Complete | Claude Code, Codex, Antigravity, OpenCode, Cursor. |
|
|
19
19
|
| Beads integration | MVP Complete | `next` wraps BV robot mode; `sync` exports story metadata JSONL. |
|
|
20
|
-
|
|
|
20
|
+
| Enterprise harness | MVP Complete | `init --enterprise` creates flow, tool-policy, and prompt harness files; `doctor --deep` verifies them. |
|
|
21
|
+
| Validation | Complete | Build and `node:test` suite pass locally with flow contract gates. |
|
|
21
22
|
| GitHub publish | Complete | Initial code pushed to `trieungoctam/speckit` at commit `7e5c582`; status docs follow-up in progress. |
|
|
22
23
|
| npm package readiness | Ready | Package scoped as `@trieungoctam/speckit`; `npm pack --dry-run` passes. |
|
|
23
24
|
|
|
24
25
|
## Next Roadmap
|
|
25
26
|
|
|
26
|
-
-
|
|
27
|
+
- Expand Speckit Enterprise Harness with richer profile and context-pack layers.
|
|
28
|
+
- Add `review --deep` with blind, edge-case, and acceptance audit prompts.
|
|
29
|
+
- Add graph commands: `graph-plan`, `insights`, and `drift`.
|
|
27
30
|
- Add GitHub trusted publishing for npm releases.
|
|
28
|
-
- Add richer config loading from `.speckit/config.yaml`.
|
|
29
|
-
- Add optional direct `br`/`bd` import once command compatibility is pinned.
|
|
30
31
|
- Add snapshot tests for full adapter file contents.
|
package/docs/product-contract.md
CHANGED
|
@@ -5,7 +5,7 @@ Speckit owns the workflow contract for enterprise Agile + TDD development with a
|
|
|
5
5
|
## Speckit Owns
|
|
6
6
|
|
|
7
7
|
- The `.speckit/` source of truth: config, rules, workflows, templates, generated artifacts.
|
|
8
|
-
- The command surface: `init`, `doctor`, `shape`, `plan`, `quick`, `next`, `
|
|
8
|
+
- The command surface: `init`, `doctor`, `start`, `shape`, `plan`, `context`, `quick`, `sync`, `triage`, `next`, `ready`, `run`, `review`, `close`.
|
|
9
9
|
- The TDD gate: code stories require red, green, and refactor evidence.
|
|
10
10
|
- The adapter compiler for Claude Code, Codex, Antigravity, OpenCode, and Cursor.
|
|
11
11
|
- The safety policy for destructive commands, secrets, production access, and review readiness.
|
|
@@ -13,15 +13,15 @@ Speckit owns the workflow contract for enterprise Agile + TDD development with a
|
|
|
13
13
|
## Speckit Delegates
|
|
14
14
|
|
|
15
15
|
- Implementation to the selected IDE and agent runtime.
|
|
16
|
-
- Product lifecycle
|
|
17
|
-
- Execution orchestration
|
|
16
|
+
- Product lifecycle, story readiness, and quality gates to Speckit-native phases.
|
|
17
|
+
- Execution orchestration to Speckit-native plan/run/test/review flows.
|
|
18
18
|
- Task graph prioritization to beads and Beads Viewer robot commands.
|
|
19
19
|
- Git, tests, CI, and deployment to project-local tooling.
|
|
20
20
|
|
|
21
21
|
## State Model
|
|
22
22
|
|
|
23
23
|
```text
|
|
24
|
-
intake -> shaped -> planned ->
|
|
24
|
+
intake -> session -> shaped -> planned -> context-ready -> synced -> triaged -> ready-for-dev -> tests-red -> running -> tests-green -> refactor -> review-ready -> closed
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Implementation starts only after
|
|
27
|
+
Implementation starts only after `speckit ready <story>` passes. Review starts only after test evidence exists.
|