@trieungoctam/speckit 0.2.0 → 0.3.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 +37 -5
- package/dist/adapters/antigravity-adapter.js +5 -5
- package/dist/adapters/claude-code-adapter.js +10 -5
- package/dist/adapters/codex-adapter.js +7 -3
- package/dist/adapters/cursor-adapter.js +10 -3
- package/dist/adapters/opencode-adapter.js +1 -0
- package/dist/cli.js +32 -0
- package/dist/commands/context.js +56 -22
- package/dist/commands/doctor.js +2 -1
- package/dist/commands/graph.d.ts +7 -0
- package/dist/commands/graph.js +51 -0
- package/dist/commands/init.js +3 -1
- package/dist/commands/memory.d.ts +6 -0
- package/dist/commands/memory.js +11 -0
- 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 +15 -2
- package/dist/commands/session.d.ts +9 -0
- package/dist/commands/session.js +49 -0
- package/dist/commands/sprint.d.ts +7 -0
- package/dist/commands/sprint.js +54 -0
- package/dist/commands/start.js +4 -3
- package/dist/commands/sync.js +5 -2
- package/dist/commands/triage.js +2 -15
- package/dist/core/agent-scaffold.d.ts +2 -0
- package/dist/core/agent-scaffold.js +57 -0
- package/dist/core/beads-mirror.d.ts +3 -0
- package/dist/core/beads-mirror.js +46 -0
- package/dist/core/memory.d.ts +1 -0
- package/dist/core/memory.js +47 -0
- package/dist/core/readiness.d.ts +13 -0
- package/dist/core/readiness.js +120 -0
- package/dist/core/scaffold.js +72 -43
- package/dist/core/session-manager.d.ts +15 -0
- package/dist/core/session-manager.js +139 -0
- package/dist/core/skill-catalog.d.ts +2 -0
- package/dist/core/skill-catalog.js +177 -0
- package/dist/core/story.d.ts +5 -0
- package/dist/core/story.js +34 -0
- package/dist/core/synced-stories.d.ts +8 -0
- package/dist/core/synced-stories.js +17 -0
- package/dist/core/templates.d.ts +2 -0
- package/dist/core/templates.js +54 -0
- package/docs/development-roadmap.md +9 -7
- package/docs/product-contract.md +7 -4
- package/docs/project-changelog.md +66 -0
- package/docs/release-checklist.md +1 -1
- package/docs/spec-enterprise-harness-plan.md +18 -1
- package/docs/workflow-model.md +23 -0
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { checkpointSession, compactSession, ensureSession, resumeSession, sessionStatus, } from "../core/session-manager.js";
|
|
2
|
+
export async function sessionCommand(options) {
|
|
3
|
+
const stdout = options.stdout ?? console;
|
|
4
|
+
switch (options.action) {
|
|
5
|
+
case "start": {
|
|
6
|
+
const session = await ensureSession(options.root, options.note ?? options.target ?? "long session");
|
|
7
|
+
stdout.log(session.id);
|
|
8
|
+
stdout.log(`${session.dir}/handoff.md`);
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
case "checkpoint": {
|
|
12
|
+
const session = await checkpointSession({
|
|
13
|
+
root: options.root,
|
|
14
|
+
note: options.note ?? options.target,
|
|
15
|
+
story: options.target,
|
|
16
|
+
});
|
|
17
|
+
stdout.log(`Checkpointed ${session.id}`);
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
case "compact": {
|
|
21
|
+
const session = await compactSession(options.root);
|
|
22
|
+
stdout.log(`${session.dir}/summary.md`);
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
case "resume": {
|
|
26
|
+
const session = await resumeSession(options.root, options.target);
|
|
27
|
+
if (!session) {
|
|
28
|
+
stdout.error("No Speckit session to resume.");
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
stdout.log(session.id);
|
|
32
|
+
stdout.log(`${session.dir}/handoff.md`);
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
case "status": {
|
|
36
|
+
const status = await sessionStatus(options.root);
|
|
37
|
+
stdout.log(options.json ? JSON.stringify(status, null, 2) : renderStatus(status));
|
|
38
|
+
return status.status === "missing" ? 1 : 0;
|
|
39
|
+
}
|
|
40
|
+
default:
|
|
41
|
+
stdout.error("Usage: speckit session start|checkpoint|compact|resume|status");
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function renderStatus(status) {
|
|
46
|
+
if (status.status === "missing")
|
|
47
|
+
return "Spec session: missing";
|
|
48
|
+
return `Spec session: ${status.id}\nState: ${status.state}\nSummary: ${status.summary}\nHandoff: ${status.handoff}`;
|
|
49
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { markdown, text, writeManagedFiles } from "../core/managed-files.js";
|
|
2
|
+
import { readSyncedStories, selectableStories } from "../core/synced-stories.js";
|
|
3
|
+
export async function sprintCommand(options) {
|
|
4
|
+
const stdout = options.stdout ?? console;
|
|
5
|
+
const stories = await readSyncedStories(options.root);
|
|
6
|
+
switch (options.action) {
|
|
7
|
+
case "plan": {
|
|
8
|
+
const selected = selectableStories(stories);
|
|
9
|
+
await writeManagedFiles(options.root, [
|
|
10
|
+
{
|
|
11
|
+
path: ".speckit/sprint/status.yaml",
|
|
12
|
+
content: text(renderStatus(stories)),
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
path: ".speckit/sprint/plan.md",
|
|
16
|
+
content: markdown(renderPlan(selected)),
|
|
17
|
+
},
|
|
18
|
+
], true);
|
|
19
|
+
stdout.log(`Planned sprint with ${selected.length} selectable stories`);
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
case "next": {
|
|
23
|
+
const next = selectableStories(stories)[0] ?? null;
|
|
24
|
+
stdout.log(options.json ? JSON.stringify({ next }, null, 2) : renderNext(next));
|
|
25
|
+
return next ? 0 : 1;
|
|
26
|
+
}
|
|
27
|
+
default:
|
|
28
|
+
stdout.error("Usage: speckit sprint plan|next [--json]");
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function renderStatus(stories) {
|
|
33
|
+
const rows = stories.map((story) => ` ${story.id}: ${story.status}`);
|
|
34
|
+
return `version: 1\nsource: .speckit/sync/beads-sync.jsonl\ndevelopment_status:\n${rows.join("\n") || " none: empty"}\n`;
|
|
35
|
+
}
|
|
36
|
+
function renderPlan(stories) {
|
|
37
|
+
const rows = stories.map((story) => `- [ ] ${story.id} (${story.status}) - \`${story.path}\``);
|
|
38
|
+
return `# Spec Sprint Plan
|
|
39
|
+
|
|
40
|
+
## Goal
|
|
41
|
+
Deliver the highest-priority ready stories while preserving TDD evidence and graph traceability.
|
|
42
|
+
|
|
43
|
+
## Selected Stories
|
|
44
|
+
${rows.join("\n") || "- No selectable stories. Run `speckit quick`, `speckit sync`, then retry."}
|
|
45
|
+
|
|
46
|
+
## Automation
|
|
47
|
+
- Run \`speckit graph triage --json\` before claiming work.
|
|
48
|
+
- Run \`speckit context <story>\` and \`speckit ready <story>\` before implementation.
|
|
49
|
+
- Run \`speckit session checkpoint\` at task boundaries.
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
function renderNext(story) {
|
|
53
|
+
return story ? `Next sprint story: ${story.id} (${story.path})` : "Next sprint story: none";
|
|
54
|
+
}
|
package/dist/commands/start.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { markdown, writeManagedFiles } from "../core/managed-files.js";
|
|
2
|
-
import {
|
|
2
|
+
import { createSession } from "../core/session-manager.js";
|
|
3
3
|
export async function startCommand(options) {
|
|
4
4
|
const stdout = options.stdout ?? console;
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const session = await createSession(options.root, options.intent);
|
|
6
|
+
const sessionId = session.id;
|
|
7
|
+
const handoffPath = `${session.dir}/handoff.md`;
|
|
7
8
|
const contextPath = ".speckit/context/current.md";
|
|
8
9
|
await writeManagedFiles(options.root, [
|
|
9
10
|
{
|
package/dist/commands/sync.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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 } from "../core/story.js";
|
|
4
|
+
import { firstHeading, frontmatterValue } from "../core/story.js";
|
|
5
|
+
import { writeBeadsMirror } from "../core/beads-mirror.js";
|
|
5
6
|
export async function syncCommand(options) {
|
|
6
7
|
const stdout = options.stdout ?? console;
|
|
7
8
|
const storiesDir = join(options.root, ".speckit", "stories");
|
|
@@ -20,7 +21,7 @@ export async function syncCommand(options) {
|
|
|
20
21
|
id: entry.replace(/\.md$/, ""),
|
|
21
22
|
path: `.speckit/stories/${entry}`,
|
|
22
23
|
title: firstHeading(content) ?? entry.replace(/\.md$/, ""),
|
|
23
|
-
status: content.includes("- [ ]") ? "open" : "ready-for-review",
|
|
24
|
+
status: frontmatterValue(content, "status") ?? (content.includes("- [ ]") ? "open" : "ready-for-review"),
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
27
|
const jsonl = stories.map((story) => JSON.stringify(story)).join("\n");
|
|
@@ -43,6 +44,8 @@ bv --robot-next --format json
|
|
|
43
44
|
`),
|
|
44
45
|
},
|
|
45
46
|
], true);
|
|
47
|
+
const beadsPath = await writeBeadsMirror(options.root, stories);
|
|
46
48
|
stdout.log(`Synced ${stories.length} stories to .speckit/sync/beads-sync.jsonl`);
|
|
49
|
+
stdout.log(`Prepared Beads Viewer mirror at ${beadsPath}`);
|
|
47
50
|
return 0;
|
|
48
51
|
}
|
package/dist/commands/triage.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { readSyncedStories, selectableStories } from "../core/synced-stories.js";
|
|
3
2
|
export async function triageCommand(options) {
|
|
4
3
|
const stdout = options.stdout ?? console;
|
|
5
4
|
const stories = await readSyncedStories(options.root);
|
|
6
|
-
const open = stories
|
|
5
|
+
const open = selectableStories(stories);
|
|
7
6
|
const ready = stories.filter((story) => story.status !== "open");
|
|
8
7
|
const report = {
|
|
9
8
|
source: ".speckit/sync/beads-sync.jsonl",
|
|
@@ -19,15 +18,3 @@ export async function triageCommand(options) {
|
|
|
19
18
|
stdout.log(open[0] ? `Next: ${open[0].id} (${open[0].path})` : "Next: none");
|
|
20
19
|
return 0;
|
|
21
20
|
}
|
|
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,57 @@
|
|
|
1
|
+
import { markdown } from "./managed-files.js";
|
|
2
|
+
import { specSkillFiles } from "./skill-catalog.js";
|
|
3
|
+
export function agentFiles() {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
path: ".speckit/agents/super-agent.md",
|
|
7
|
+
content: markdown(`# Spec Super Agent
|
|
8
|
+
|
|
9
|
+
## Mission
|
|
10
|
+
|
|
11
|
+
Act as the controller for Speckit enterprise work. Do not be the only worker when context isolation is useful; route work through focused skills and subagent handoffs.
|
|
12
|
+
|
|
13
|
+
## Load Order
|
|
14
|
+
|
|
15
|
+
1. \`.speckit/memory/project-context.md\`
|
|
16
|
+
2. \`.speckit/sessions/active.md\` and the linked session summary
|
|
17
|
+
3. \`.speckit/skills/catalog.md\`
|
|
18
|
+
4. \`.speckit/context/current.md\`
|
|
19
|
+
5. \`.speckit/context/subagent-handoff.md\`
|
|
20
|
+
|
|
21
|
+
## Routing Rules
|
|
22
|
+
|
|
23
|
+
- Use planning skills before implementation.
|
|
24
|
+
- Select the smallest matching Speckit skill for the current phase.
|
|
25
|
+
- Route by the story state first, then by the requested action.
|
|
26
|
+
- Use TDD execution skills only after \`speckit ready <story>\` passes.
|
|
27
|
+
- Use graph skills before choosing or reordering work.
|
|
28
|
+
- Use session skills at every task boundary.
|
|
29
|
+
- Use review skills before closure.
|
|
30
|
+
|
|
31
|
+
## Delegation Contract
|
|
32
|
+
|
|
33
|
+
Every handoff must include:
|
|
34
|
+
|
|
35
|
+
- Work context path.
|
|
36
|
+
- Reports path.
|
|
37
|
+
- Plans path.
|
|
38
|
+
- Files to read.
|
|
39
|
+
- Files to modify.
|
|
40
|
+
- Acceptance criteria.
|
|
41
|
+
- Constraints and stop conditions.
|
|
42
|
+
|
|
43
|
+
Subagents must finish with one status: \`DONE\`, \`DONE_WITH_CONCERNS\`, \`BLOCKED\`, or \`NEEDS_CONTEXT\`.
|
|
44
|
+
|
|
45
|
+
## Long Session Contract
|
|
46
|
+
|
|
47
|
+
- Keep durable facts in files, not in chat.
|
|
48
|
+
- Checkpoint after red, green, refactor, and review.
|
|
49
|
+
- Compact before handoff, after noisy tool output, or before switching stories.
|
|
50
|
+
- Prefer focused subagent handoffs over passing full conversation history.
|
|
51
|
+
- Hydrate runtime tasks from unchecked plan/story checkboxes at session start.
|
|
52
|
+
- Sync completed runtime tasks back to durable plan/story files before close.
|
|
53
|
+
`),
|
|
54
|
+
},
|
|
55
|
+
...specSkillFiles(),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { readSyncedStories } from "./synced-stories.js";
|
|
4
|
+
export async function writeBeadsMirror(root, stories) {
|
|
5
|
+
const path = ".beads/beads.jsonl";
|
|
6
|
+
const now = new Date().toISOString();
|
|
7
|
+
const jsonl = stories
|
|
8
|
+
.map((story) => JSON.stringify(toBeadIssue(story, now)))
|
|
9
|
+
.join("\n");
|
|
10
|
+
const target = join(root, path);
|
|
11
|
+
await mkdir(dirname(target), { recursive: true });
|
|
12
|
+
await writeFile(target, `${jsonl}${jsonl ? "\n" : ""}`, "utf8");
|
|
13
|
+
return path;
|
|
14
|
+
}
|
|
15
|
+
export async function prepareBeadsMirror(root) {
|
|
16
|
+
return writeBeadsMirror(root, await readSyncedStories(root));
|
|
17
|
+
}
|
|
18
|
+
function toBeadIssue(story, timestamp) {
|
|
19
|
+
return {
|
|
20
|
+
id: story.id,
|
|
21
|
+
title: story.title,
|
|
22
|
+
description: `Speckit story: ${story.path}`,
|
|
23
|
+
status: mapStatus(story.status),
|
|
24
|
+
priority: 2,
|
|
25
|
+
issue_type: "task",
|
|
26
|
+
labels: ["speckit", "story"],
|
|
27
|
+
created_at: timestamp,
|
|
28
|
+
updated_at: timestamp,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function mapStatus(status) {
|
|
32
|
+
switch (status) {
|
|
33
|
+
case "in-progress":
|
|
34
|
+
return "in_progress";
|
|
35
|
+
case "ready-for-review":
|
|
36
|
+
case "review-ready":
|
|
37
|
+
return "review";
|
|
38
|
+
case "closed":
|
|
39
|
+
case "done":
|
|
40
|
+
return "closed";
|
|
41
|
+
case "draft":
|
|
42
|
+
return "draft";
|
|
43
|
+
default:
|
|
44
|
+
return "open";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function refreshMemory(root: string): Promise<string[]>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { markdown, writeManagedFiles } from "./managed-files.js";
|
|
2
|
+
export async function refreshMemory(root) {
|
|
3
|
+
const files = [
|
|
4
|
+
{
|
|
5
|
+
path: ".speckit/memory/project-context.md",
|
|
6
|
+
content: markdown(`# Spec Project Context
|
|
7
|
+
|
|
8
|
+
## Technology Stack & Versions
|
|
9
|
+
- Update this section with project-specific runtime, framework, test, and deployment facts.
|
|
10
|
+
|
|
11
|
+
## Critical Implementation Rules
|
|
12
|
+
- Prefer existing project patterns over generic framework defaults.
|
|
13
|
+
- Keep implementation scoped to the active story and acceptance criteria.
|
|
14
|
+
- Record red-green-refactor evidence for code stories.
|
|
15
|
+
- Use robot-safe graph commands only.
|
|
16
|
+
|
|
17
|
+
## Context Loading Rules
|
|
18
|
+
- Load this file before implementation, review, sprint planning, and session resume.
|
|
19
|
+
- Treat this file as durable guidance, not as proof that the current environment still matches it.
|
|
20
|
+
`),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
path: ".speckit/memory/decisions.md",
|
|
24
|
+
content: markdown(`# Spec Decisions
|
|
25
|
+
|
|
26
|
+
## Active Decisions
|
|
27
|
+
- Record durable architecture and workflow decisions here.
|
|
28
|
+
|
|
29
|
+
## Superseded Decisions
|
|
30
|
+
- Move stale decisions here with the replacement decision and date.
|
|
31
|
+
`),
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
path: ".speckit/memory/lessons.md",
|
|
35
|
+
content: markdown(`# Spec Lessons
|
|
36
|
+
|
|
37
|
+
## Repeated Mistakes To Avoid
|
|
38
|
+
- Add a lesson when an agent repeats a mistake or review catches a missed project rule.
|
|
39
|
+
|
|
40
|
+
## Useful Verification Commands
|
|
41
|
+
- Add commands that reliably validate this project.
|
|
42
|
+
`),
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
const results = await writeManagedFiles(root, files, false);
|
|
46
|
+
return results.map((result) => result.path);
|
|
47
|
+
}
|
|
@@ -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.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { markdown, text } from "./managed-files.js";
|
|
2
2
|
import { agilePolicy, enterpriseSafetyPolicy, tddPolicy, workflowReview, workflowShape, workflowTddRun, } from "./policy.js";
|
|
3
|
+
import { storyTemplate, tddEvidenceTemplate } from "./templates.js";
|
|
3
4
|
export function coreFiles() {
|
|
4
5
|
return [
|
|
5
6
|
{
|
|
@@ -13,6 +14,14 @@ tdd:
|
|
|
13
14
|
task_graph:
|
|
14
15
|
provider: beads
|
|
15
16
|
bv_robot_only: true
|
|
17
|
+
memory:
|
|
18
|
+
project_context: .speckit/memory/project-context.md
|
|
19
|
+
session_dir: .speckit/sessions
|
|
20
|
+
skills:
|
|
21
|
+
catalog: .speckit/skills/catalog.md
|
|
22
|
+
super_agent: .speckit/agents/super-agent.md
|
|
23
|
+
sprint:
|
|
24
|
+
status: .speckit/sprint/status.yaml
|
|
16
25
|
adapters:
|
|
17
26
|
enabled: []`),
|
|
18
27
|
},
|
|
@@ -27,45 +36,11 @@ adapters:
|
|
|
27
36
|
{ path: ".speckit/workflows/review.md", content: markdown(workflowReview) },
|
|
28
37
|
{
|
|
29
38
|
path: ".speckit/templates/story.md",
|
|
30
|
-
content: markdown(
|
|
31
|
-
|
|
32
|
-
## Intent
|
|
33
|
-
{{intent}}
|
|
34
|
-
|
|
35
|
-
## Acceptance Criteria
|
|
36
|
-
- Given ...
|
|
37
|
-
- When ...
|
|
38
|
-
- Then ...
|
|
39
|
-
|
|
40
|
-
## TDD Checklist
|
|
41
|
-
- [ ] Test targets identified
|
|
42
|
-
- [ ] Red evidence recorded
|
|
43
|
-
- [ ] Green evidence recorded
|
|
44
|
-
- [ ] Refactor validation recorded
|
|
45
|
-
|
|
46
|
-
## Notes
|
|
47
|
-
- Risks:
|
|
48
|
-
- Dependencies:
|
|
49
|
-
`),
|
|
39
|
+
content: markdown(storyTemplate),
|
|
50
40
|
},
|
|
51
41
|
{
|
|
52
42
|
path: ".speckit/templates/tdd-evidence.md",
|
|
53
|
-
content: markdown(
|
|
54
|
-
|
|
55
|
-
## Test Intent
|
|
56
|
-
|
|
57
|
-
## Red
|
|
58
|
-
- Command:
|
|
59
|
-
- Result:
|
|
60
|
-
|
|
61
|
-
## Green
|
|
62
|
-
- Command:
|
|
63
|
-
- Result:
|
|
64
|
-
|
|
65
|
-
## Refactor
|
|
66
|
-
- Command:
|
|
67
|
-
- Result:
|
|
68
|
-
`),
|
|
43
|
+
content: markdown(tddEvidenceTemplate),
|
|
69
44
|
},
|
|
70
45
|
];
|
|
71
46
|
}
|
|
@@ -76,12 +51,14 @@ export function enterpriseFiles() {
|
|
|
76
51
|
content: markdown(`# Spec Flow
|
|
77
52
|
|
|
78
53
|
## Order
|
|
79
|
-
start ->
|
|
54
|
+
start -> memory -> sprint -> context -> sync -> triage -> ready -> run -> checkpoint -> review -> close
|
|
80
55
|
|
|
81
56
|
## Traceability
|
|
82
57
|
- A session links the original idea to every downstream artifact.
|
|
58
|
+
- Project memory links durable rules to every new session.
|
|
83
59
|
- A story links acceptance criteria to TDD evidence.
|
|
84
60
|
- Sync links stories to graph-ready JSONL.
|
|
61
|
+
- Session checkpoints preserve file changes, decisions, and next steps across compaction.
|
|
85
62
|
- Close links review output back to story and graph sync.
|
|
86
63
|
`),
|
|
87
64
|
},
|
|
@@ -105,12 +82,64 @@ graph:
|
|
|
105
82
|
path: ".speckit/prompts/spec-run.md",
|
|
106
83
|
content: markdown(`# Spec Run Prompt
|
|
107
84
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
85
|
+
## Required Inputs
|
|
86
|
+
|
|
87
|
+
- Current context: \`.speckit/context/current.md\`
|
|
88
|
+
- Subagent handoff: \`.speckit/context/subagent-handoff.md\`
|
|
89
|
+
- Project memory: \`.speckit/memory/project-context.md\`
|
|
90
|
+
- Active session summary: \`.speckit/sessions/active.md\` and the linked \`summary.md\`
|
|
91
|
+
- Active artifact log: linked \`artifact-log.md\`
|
|
92
|
+
- Super agent router: \`.speckit/agents/super-agent.md\`
|
|
93
|
+
- Skill catalog: \`.speckit/skills/catalog.md\`
|
|
94
|
+
- Story with acceptance criteria
|
|
95
|
+
- Matching TDD evidence file
|
|
96
|
+
- Tool policy: \`.speckit/tool-policy.yaml\`
|
|
97
|
+
|
|
98
|
+
## Status Preconditions
|
|
99
|
+
|
|
100
|
+
- Story status is \`ready-for-dev\`.
|
|
101
|
+
- Context status is \`fresh\`.
|
|
102
|
+
- \`speckit ready <story>\` returns ready.
|
|
103
|
+
- \`speckit session status\` identifies the active session.
|
|
104
|
+
|
|
105
|
+
## Allowed Edits
|
|
106
|
+
|
|
107
|
+
- Implementation files needed for the story.
|
|
108
|
+
- Matching tests.
|
|
109
|
+
- Linked TDD evidence file.
|
|
110
|
+
- Story completion checkboxes and implementation notes.
|
|
111
|
+
|
|
112
|
+
## Execution Contract
|
|
113
|
+
|
|
114
|
+
Use the red-green-refactor loop.
|
|
115
|
+
|
|
116
|
+
1. Read the current context before touching code.
|
|
117
|
+
2. Read the super agent router and select the smallest matching Speckit skill.
|
|
118
|
+
3. Confirm the story path, acceptance criteria, and evidence path.
|
|
119
|
+
4. Write or run the smallest failing test first.
|
|
120
|
+
5. Record red evidence before implementation.
|
|
121
|
+
6. Implement the smallest change that satisfies the failing test.
|
|
122
|
+
7. Record green evidence after tests pass.
|
|
123
|
+
8. Refactor only while tests stay green, then record validation.
|
|
124
|
+
9. Use graph commands only through robot-safe flags such as \`bv --robot-next --format json\`.
|
|
125
|
+
10. Run \`speckit session checkpoint --note "<phase complete>"\` after red, green, refactor, and review boundaries.
|
|
126
|
+
11. Run \`speckit session compact\` before context gets noisy or before handing off to another agent.
|
|
127
|
+
12. Run \`speckit review\` before handoff.
|
|
128
|
+
|
|
129
|
+
## Stop Conditions
|
|
130
|
+
|
|
131
|
+
- Stop if acceptance criteria are missing.
|
|
132
|
+
- Stop if the evidence file cannot be identified.
|
|
133
|
+
- Stop if \`speckit ready <story>\` reports blocked.
|
|
134
|
+
- Stop if active session state cannot be written.
|
|
135
|
+
- Stop before destructive commands, production changes, or secret access.
|
|
136
|
+
|
|
137
|
+
## Completion Signal
|
|
138
|
+
|
|
139
|
+
- All acceptance criteria satisfied.
|
|
140
|
+
- TDD evidence status can be advanced through red, green, and refactor.
|
|
141
|
+
- Session checkpoint and compact summary are current.
|
|
142
|
+
- Review handoff is ready.
|
|
114
143
|
`),
|
|
115
144
|
},
|
|
116
145
|
];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type SessionCheckpointOptions = {
|
|
2
|
+
root: string;
|
|
3
|
+
note?: string;
|
|
4
|
+
story?: string;
|
|
5
|
+
};
|
|
6
|
+
export type SessionRef = {
|
|
7
|
+
id: string;
|
|
8
|
+
dir: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function ensureSession(root: string, intent?: string): Promise<SessionRef>;
|
|
11
|
+
export declare function createSession(root: string, intent?: string): Promise<SessionRef>;
|
|
12
|
+
export declare function checkpointSession(options: SessionCheckpointOptions): Promise<SessionRef>;
|
|
13
|
+
export declare function compactSession(root: string): Promise<SessionRef>;
|
|
14
|
+
export declare function resumeSession(root: string, id?: string): Promise<SessionRef | undefined>;
|
|
15
|
+
export declare function sessionStatus(root: string): Promise<Record<string, string>>;
|