@trieungoctam/speckit 0.2.2 → 0.3.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 +43 -6
- package/dist/adapters/antigravity-adapter.js +4 -4
- package/dist/adapters/claude-code-adapter.js +23 -5
- package/dist/adapters/codex-adapter.js +7 -3
- package/dist/adapters/cursor-adapter.js +30 -3
- package/dist/adapters/opencode-adapter.js +1 -1
- package/dist/cli.js +41 -0
- package/dist/commands/doctor.js +12 -4
- 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/permissions.d.ts +8 -0
- package/dist/commands/permissions.js +18 -0
- 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 +3 -0
- package/dist/commands/triage.js +2 -15
- package/dist/commands/validate.d.ts +6 -0
- package/dist/commands/validate.js +17 -0
- 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/permission-auditor.d.ts +10 -0
- package/dist/core/permission-auditor.js +65 -0
- package/dist/core/permission-policy.d.ts +6 -0
- package/dist/core/permission-policy.js +93 -0
- package/dist/core/scaffold.js +35 -63
- package/dist/core/session-manager.d.ts +15 -0
- package/dist/core/session-manager.js +139 -0
- package/dist/core/skill-catalog.d.ts +3 -0
- package/dist/core/skill-catalog.js +180 -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/dist/core/workflow-contract.d.ts +7 -0
- package/dist/core/workflow-contract.js +38 -0
- package/dist/core/workflow-validator.d.ts +6 -0
- package/dist/core/workflow-validator.js +133 -0
- package/docs/development-roadmap.md +9 -5
- package/docs/permission-rules-research.md +265 -0
- package/docs/product-contract.md +9 -4
- package/docs/project-changelog.md +46 -0
- package/docs/spec-enterprise-harness-plan.md +18 -1
- package/docs/use-cases.md +206 -0
- package/docs/workflow-model.md +23 -0
- package/package.json +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { slugify, timestamp } from "./slug.js";
|
|
4
|
+
export async function ensureSession(root, intent = "long session") {
|
|
5
|
+
const active = await readActiveSession(root);
|
|
6
|
+
if (active)
|
|
7
|
+
return active;
|
|
8
|
+
return createSession(root, intent);
|
|
9
|
+
}
|
|
10
|
+
export async function createSession(root, intent = "long session") {
|
|
11
|
+
const id = `${timestamp()}-${slugify(intent)}`;
|
|
12
|
+
const ref = { id, dir: `.speckit/sessions/${id}` };
|
|
13
|
+
await mkdir(join(root, ref.dir), { recursive: true });
|
|
14
|
+
await writeActiveSession(root, ref);
|
|
15
|
+
await writeFile(join(root, ref.dir, "events.jsonl"), "", "utf8");
|
|
16
|
+
await writeFile(join(root, ref.dir, "artifact-log.md"), "# Spec Artifact Log\n\n", "utf8");
|
|
17
|
+
await writeFile(join(root, ref.dir, "summary.md"), baseSummary(intent), "utf8");
|
|
18
|
+
await writeState(root, ref, { phase: "started", status: "active", story: "pending" });
|
|
19
|
+
await writeHandoff(root, ref, "Start from the active Speckit session state.");
|
|
20
|
+
return ref;
|
|
21
|
+
}
|
|
22
|
+
export async function checkpointSession(options) {
|
|
23
|
+
const ref = await ensureSession(options.root, options.note ?? "checkpoint");
|
|
24
|
+
const event = {
|
|
25
|
+
type: "checkpoint",
|
|
26
|
+
at: new Date().toISOString(),
|
|
27
|
+
note: options.note ?? "Manual checkpoint",
|
|
28
|
+
story: options.story,
|
|
29
|
+
};
|
|
30
|
+
await appendFile(join(options.root, ref.dir, "events.jsonl"), `${JSON.stringify(event)}\n`, "utf8");
|
|
31
|
+
await appendFile(join(options.root, ref.dir, "artifact-log.md"), `## ${event.at}\n- Note: ${event.note}\n${options.story ? `- Story: \`${options.story}\`\n` : ""}\n`, "utf8");
|
|
32
|
+
await writeState(options.root, ref, {
|
|
33
|
+
phase: "checkpointed",
|
|
34
|
+
status: "active",
|
|
35
|
+
story: options.story ?? "pending",
|
|
36
|
+
});
|
|
37
|
+
return ref;
|
|
38
|
+
}
|
|
39
|
+
export async function compactSession(root) {
|
|
40
|
+
const ref = await ensureSession(root, "compact");
|
|
41
|
+
const [state, context, handoff, artifactLog] = await Promise.all([
|
|
42
|
+
readOptional(root, `${ref.dir}/state.yaml`),
|
|
43
|
+
readOptional(root, ".speckit/context/current.md"),
|
|
44
|
+
readOptional(root, ".speckit/context/subagent-handoff.md"),
|
|
45
|
+
readOptional(root, `${ref.dir}/artifact-log.md`),
|
|
46
|
+
]);
|
|
47
|
+
const summary = `# Spec Session Summary
|
|
48
|
+
|
|
49
|
+
## Session Intent
|
|
50
|
+
Preserve the active Speckit workflow so another agent or compacted session can continue safely.
|
|
51
|
+
|
|
52
|
+
## Current State
|
|
53
|
+
${state || "- No state recorded yet."}
|
|
54
|
+
|
|
55
|
+
## Active Context
|
|
56
|
+
${extractCompactSection(context)}
|
|
57
|
+
|
|
58
|
+
## Files And Artifacts
|
|
59
|
+
${extractCompactSection(artifactLog)}
|
|
60
|
+
|
|
61
|
+
## Decisions Made
|
|
62
|
+
- Trust current files over stale memory.
|
|
63
|
+
- Keep implementation bounded by the selected story and readiness gate.
|
|
64
|
+
|
|
65
|
+
## Next Steps
|
|
66
|
+
1. Read \`.speckit/memory/project-context.md\`.
|
|
67
|
+
2. Read \`.speckit/context/current.md\` and \`.speckit/context/subagent-handoff.md\`.
|
|
68
|
+
3. Run \`speckit session status\`.
|
|
69
|
+
4. Run \`speckit ready <story>\` before implementation.
|
|
70
|
+
|
|
71
|
+
## Resume Handoff
|
|
72
|
+
${extractCompactSection(handoff)}
|
|
73
|
+
`;
|
|
74
|
+
await writeFile(join(root, ref.dir, "summary.md"), summary, "utf8");
|
|
75
|
+
await writeHandoff(root, ref, "Resume from the compacted session summary before loading task context.");
|
|
76
|
+
return ref;
|
|
77
|
+
}
|
|
78
|
+
export async function resumeSession(root, id) {
|
|
79
|
+
const ref = id ? { id, dir: `.speckit/sessions/${id}` } : await readActiveSession(root);
|
|
80
|
+
if (!ref)
|
|
81
|
+
return undefined;
|
|
82
|
+
await writeActiveSession(root, ref);
|
|
83
|
+
await writeHandoff(root, ref, "Resume this session and continue from summary, context, and artifact log.");
|
|
84
|
+
return ref;
|
|
85
|
+
}
|
|
86
|
+
export async function sessionStatus(root) {
|
|
87
|
+
const ref = await readActiveSession(root);
|
|
88
|
+
if (!ref)
|
|
89
|
+
return { status: "missing" };
|
|
90
|
+
return {
|
|
91
|
+
status: "active",
|
|
92
|
+
id: ref.id,
|
|
93
|
+
dir: ref.dir,
|
|
94
|
+
state: `${ref.dir}/state.yaml`,
|
|
95
|
+
summary: `${ref.dir}/summary.md`,
|
|
96
|
+
handoff: `${ref.dir}/handoff.md`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function readActiveSession(root) {
|
|
100
|
+
try {
|
|
101
|
+
const content = await readFile(join(root, ".speckit", "sessions", "active.md"), "utf8");
|
|
102
|
+
const id = content.match(/session:\s*([^\s]+)/)?.[1];
|
|
103
|
+
return id ? { id, dir: `.speckit/sessions/${id}` } : undefined;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function writeActiveSession(root, ref) {
|
|
110
|
+
await writeText(root, ".speckit/sessions/active.md", `# Active Spec Session\n\nsession: ${ref.id}\n`);
|
|
111
|
+
}
|
|
112
|
+
async function writeState(root, ref, state) {
|
|
113
|
+
const body = Object.entries({ session: ref.id, updated: new Date().toISOString(), ...state })
|
|
114
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
115
|
+
.join("\n");
|
|
116
|
+
await writeText(root, `${ref.dir}/state.yaml`, `${body}\n`);
|
|
117
|
+
}
|
|
118
|
+
async function writeHandoff(root, ref, note) {
|
|
119
|
+
await writeText(root, `${ref.dir}/handoff.md`, `# Spec Session Handoff\n\n${note}\n\n## Load Order\n1. \`${ref.dir}/summary.md\`\n2. \`${ref.dir}/state.yaml\`\n3. \`${ref.dir}/artifact-log.md\`\n4. \`.speckit/context/current.md\`\n`);
|
|
120
|
+
}
|
|
121
|
+
async function writeText(root, path, content) {
|
|
122
|
+
const target = join(root, path);
|
|
123
|
+
await mkdir(dirname(target), { recursive: true });
|
|
124
|
+
await writeFile(target, content, "utf8");
|
|
125
|
+
}
|
|
126
|
+
async function readOptional(root, path) {
|
|
127
|
+
try {
|
|
128
|
+
return await readFile(join(root, path), "utf8");
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return "";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function baseSummary(intent) {
|
|
135
|
+
return `# Spec Session Summary\n\n## Session Intent\n${intent}\n\n## Current State\n- Session started.\n\n## Next Steps\n1. Build context.\n2. Sync graph.\n3. Run readiness.\n`;
|
|
136
|
+
}
|
|
137
|
+
function extractCompactSection(content) {
|
|
138
|
+
return content.trim() ? content.trim().split("\n").slice(0, 40).join("\n") : "- Not recorded.";
|
|
139
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { json, markdown } from "./managed-files.js";
|
|
2
|
+
const selectedSources = [
|
|
3
|
+
"brainstorm",
|
|
4
|
+
"research",
|
|
5
|
+
"docs-seeker",
|
|
6
|
+
"plan",
|
|
7
|
+
"project-management",
|
|
8
|
+
"scout",
|
|
9
|
+
"context-engineering",
|
|
10
|
+
"kanban",
|
|
11
|
+
"implementation-runner",
|
|
12
|
+
"test",
|
|
13
|
+
"debug",
|
|
14
|
+
"fix",
|
|
15
|
+
"code-review",
|
|
16
|
+
"security-scan",
|
|
17
|
+
"docs",
|
|
18
|
+
"journal",
|
|
19
|
+
"git",
|
|
20
|
+
"ship",
|
|
21
|
+
];
|
|
22
|
+
const skills = [
|
|
23
|
+
skill("spec-shape", "shape", "Turn a rough request into a bounded Agile spec before planning.", [
|
|
24
|
+
"Clarify business goal, users, constraints, and non-goals.",
|
|
25
|
+
"Split large ideas into independent stories with acceptance criteria.",
|
|
26
|
+
"Challenge over-engineered scope before it reaches implementation.",
|
|
27
|
+
]),
|
|
28
|
+
skill("spec-research", "research", "Validate external tools, docs, or architecture choices before planning.", [
|
|
29
|
+
"Define recency and source-quality requirements before searching.",
|
|
30
|
+
"Prefer official docs, primary repos, and current release notes.",
|
|
31
|
+
"Summarize recommendations, risks, and rejected options in reports.",
|
|
32
|
+
]),
|
|
33
|
+
skill("spec-plan", "plan", "Create PRD, architecture, story, and evidence skeletons.", [
|
|
34
|
+
"Check unfinished plans before creating new work.",
|
|
35
|
+
"Write phases with files, dependencies, success criteria, and rollback.",
|
|
36
|
+
"Keep plan state durable so sessions can rehydrate tasks later.",
|
|
37
|
+
]),
|
|
38
|
+
skill("spec-context", "context", "Build the smallest useful story context and subagent handoff.", [
|
|
39
|
+
"Read project memory, active session, story, evidence, and relevant files.",
|
|
40
|
+
"List files to read and files to modify separately.",
|
|
41
|
+
"Avoid passing full chat history to implementation agents.",
|
|
42
|
+
]),
|
|
43
|
+
skill("spec-session", "session", "Manage checkpoint, compaction, resume, and artifact-log discipline.", [
|
|
44
|
+
"Checkpoint after red, green, refactor, review, and scope changes.",
|
|
45
|
+
"Compact noisy context into durable summaries before handoff.",
|
|
46
|
+
"Sync runtime task progress back to plan and story files.",
|
|
47
|
+
]),
|
|
48
|
+
skill("spec-graph", "graph", "Use sprint and graph robot outputs to choose safe, unblocked work.", [
|
|
49
|
+
"Run Beads Viewer through robot-safe commands only.",
|
|
50
|
+
"Prefer unblocked, high-value stories with clear evidence paths.",
|
|
51
|
+
"Mirror story state to graph artifacts before triage or next selection.",
|
|
52
|
+
]),
|
|
53
|
+
skill("spec-tdd", "run", "Run a ready story through red-green-refactor with evidence and checkpoints.", [
|
|
54
|
+
"Require ready-for-dev status and current context before code edits.",
|
|
55
|
+
"Write or run the smallest failing test before implementation.",
|
|
56
|
+
"Record red, green, and refactor evidence with session checkpoints.",
|
|
57
|
+
]),
|
|
58
|
+
skill("spec-test", "test", "Select and run verification after implementation.", [
|
|
59
|
+
"Map changed files to focused tests first, then broaden when risk is high.",
|
|
60
|
+
"Run typecheck or build before claiming code is valid.",
|
|
61
|
+
"Never ignore failing tests; report root cause or blocker.",
|
|
62
|
+
]),
|
|
63
|
+
skill("spec-debug", "debug", "Diagnose failures with evidence before changing code.", [
|
|
64
|
+
"Capture exact failing output and pre-fix state.",
|
|
65
|
+
"Trace root cause through callers, dependencies, and recent changes.",
|
|
66
|
+
"Fix the cause, then verify against the original failure.",
|
|
67
|
+
]),
|
|
68
|
+
skill("spec-review", "review", "Review acceptance coverage, TDD evidence, safety, docs, and session freshness.", [
|
|
69
|
+
"Review spec compliance before code quality opinions.",
|
|
70
|
+
"Prioritize correctness, security, regressions, and missing tests.",
|
|
71
|
+
"Block closure when evidence, context, or session state is stale.",
|
|
72
|
+
]),
|
|
73
|
+
skill("spec-docs", "docs", "Update durable docs, changelog, and lessons after behavior changes.", [
|
|
74
|
+
"Update roadmap and changelog when milestone or behavior changes.",
|
|
75
|
+
"Record decisions and lessons in project memory when reusable.",
|
|
76
|
+
"Keep docs aligned with actual implemented behavior.",
|
|
77
|
+
]),
|
|
78
|
+
skill("spec-ship", "ship", "Prepare clean release handoff after tests and review pass.", [
|
|
79
|
+
"Check git diff for unrelated changes and secrets before commit.",
|
|
80
|
+
"Use focused conventional commits and release notes.",
|
|
81
|
+
"Do not ship while tests, review, or evidence gates are failing.",
|
|
82
|
+
]),
|
|
83
|
+
];
|
|
84
|
+
function skill(name, phase, purpose, practices) {
|
|
85
|
+
return { name, phase, purpose, practices };
|
|
86
|
+
}
|
|
87
|
+
export function specSkillFiles() {
|
|
88
|
+
return [catalogFile(), schemaFile(), ...skills.map(skillFile)];
|
|
89
|
+
}
|
|
90
|
+
export function specSkillNames() {
|
|
91
|
+
return skills.map((skill) => skill.name);
|
|
92
|
+
}
|
|
93
|
+
function catalogFile() {
|
|
94
|
+
return {
|
|
95
|
+
path: ".speckit/skills/catalog.md",
|
|
96
|
+
content: markdown(`# Spec Skill Catalog
|
|
97
|
+
|
|
98
|
+
This catalog is intentionally smaller than a broad general-purpose skill set. Speckit keeps only the skills required for enterprise Agile, TDD, long sessions, graph-guided work, and release hygiene.
|
|
99
|
+
|
|
100
|
+
## Core Skills
|
|
101
|
+
|
|
102
|
+
| Skill | Inspired By | Use When | Required Inputs |
|
|
103
|
+
| --- | --- | --- | --- |
|
|
104
|
+
| spec-shape | brainstorm, scenario | Turning rough intent into a bounded spec | user intent, project memory |
|
|
105
|
+
| spec-research | research, docs-seeker | Validating external choices and current docs | scope, source criteria |
|
|
106
|
+
| spec-plan | plan, project-management | Creating PRD, architecture, stories, and evidence skeletons | spec brief, project memory |
|
|
107
|
+
| spec-context | scout, context-engineering | Building current story context and handoff | story, evidence, session state |
|
|
108
|
+
| spec-graph | kanban, plans-kanban, gkg | Choosing and sequencing safe work | synced stories, robot graph output |
|
|
109
|
+
| spec-session | context-engineering, watzup | Preserving long-running work | active session, artifact log |
|
|
110
|
+
| spec-tdd | implementation-runner, test | Implementing code with red-green-refactor | ready story, context, evidence |
|
|
111
|
+
| spec-test | test, web-testing | Selecting and running verification | changed files, acceptance criteria |
|
|
112
|
+
| spec-debug | debug, fix, sequential-thinking | Diagnosing failures before changing code | failing output, impact scope |
|
|
113
|
+
| spec-review | code-review, security-scan | Reviewing code and artifacts | diff, story, evidence, session log |
|
|
114
|
+
| spec-docs | docs, journal, retro | Updating docs and durable decisions | changed behavior, release notes |
|
|
115
|
+
| spec-ship | git, ship, deploy | Preparing clean release handoff | passing tests, review result |
|
|
116
|
+
|
|
117
|
+
## Selection Policy
|
|
118
|
+
|
|
119
|
+
- Pick the smallest skill that covers the current phase.
|
|
120
|
+
- Load skill detail only when that phase is active.
|
|
121
|
+
- Never mix planning and implementation in the same skill invocation.
|
|
122
|
+
- Prefer file handoff over chat handoff for subagents.
|
|
123
|
+
- Do not import broad domain skills unless the current story explicitly needs that domain.
|
|
124
|
+
- Use \`spec-debug\` before fixes when the root cause is not proven.
|
|
125
|
+
- Use \`spec-test\` after implementation and before review.
|
|
126
|
+
`),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function schemaFile() {
|
|
130
|
+
return {
|
|
131
|
+
path: ".speckit/skills/schema.json",
|
|
132
|
+
content: json({
|
|
133
|
+
version: 1,
|
|
134
|
+
required: ["name", "purpose", "phase", "inputs", "outputs", "stop_conditions"],
|
|
135
|
+
statuses: ["DONE", "DONE_WITH_CONCERNS", "BLOCKED", "NEEDS_CONTEXT"],
|
|
136
|
+
selected_from: selectedSources,
|
|
137
|
+
}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function skillFile(skill) {
|
|
141
|
+
return {
|
|
142
|
+
path: `.speckit/skills/${skill.name}.md`,
|
|
143
|
+
content: markdown(`# ${skill.name}
|
|
144
|
+
|
|
145
|
+
## Purpose
|
|
146
|
+
|
|
147
|
+
${skill.purpose}
|
|
148
|
+
|
|
149
|
+
## Phase
|
|
150
|
+
|
|
151
|
+
${skill.phase}
|
|
152
|
+
|
|
153
|
+
## Required Context
|
|
154
|
+
|
|
155
|
+
- \`.speckit/memory/project-context.md\`
|
|
156
|
+
- \`.speckit/sessions/active.md\`
|
|
157
|
+
- \`.speckit/context/current.md\` when story-scoped
|
|
158
|
+
- \`.speckit/context/subagent-handoff.md\` when delegating
|
|
159
|
+
|
|
160
|
+
## Practices
|
|
161
|
+
|
|
162
|
+
${skill.practices.map((practice) => `- ${practice}`).join("\n")}
|
|
163
|
+
|
|
164
|
+
## Stop Conditions
|
|
165
|
+
|
|
166
|
+
- Missing acceptance criteria.
|
|
167
|
+
- Missing or stale active session.
|
|
168
|
+
- Missing evidence path for code work.
|
|
169
|
+
- Blocked story or unresolved \`NEEDS_CONTEXT\` handoff.
|
|
170
|
+
|
|
171
|
+
## Output Contract
|
|
172
|
+
|
|
173
|
+
- State what was read.
|
|
174
|
+
- State what changed.
|
|
175
|
+
- State the next Speckit command.
|
|
176
|
+
- Write durable progress to the appropriate Speckit artifact.
|
|
177
|
+
- End delegated work with \`DONE\`, \`DONE_WITH_CONCERNS\`, \`BLOCKED\`, or \`NEEDS_CONTEXT\`.
|
|
178
|
+
`),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
export async function readSyncedStories(root) {
|
|
4
|
+
try {
|
|
5
|
+
const content = await readFile(join(root, ".speckit", "sync", "beads-sync.jsonl"), "utf8");
|
|
6
|
+
return content
|
|
7
|
+
.split("\n")
|
|
8
|
+
.filter(Boolean)
|
|
9
|
+
.map((line) => JSON.parse(line));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function selectableStories(stories) {
|
|
16
|
+
return stories.filter((story) => ["open", "ready-for-dev", "in-progress"].includes(story.status));
|
|
17
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const storyTemplate = "---\nstatus: draft\nevidence: .speckit/evidence/{{slug}}-tdd-evidence.md\ncontext: pending\n---\n\n# Story: {{title}}\n\n## Intent\n{{intent}}\n\n## Acceptance Criteria\n- Given ...\n- When ...\n- Then ...\n\n## TDD Checklist\n- [ ] Test targets identified\n- [ ] Red evidence recorded\n- [ ] Green evidence recorded\n- [ ] Refactor validation recorded\n\n## Notes\n- Risks:\n- Dependencies:\n\n## Spec Anti-Mistake Checklist\n- Reuse existing project patterns before adding new files.\n- Verify file locations before editing.\n- Do not introduce new libraries without explicit need.\n- Preserve existing behavior unless an acceptance criterion requires change.\n- Capture previous-story learnings if this continues prior work.\n";
|
|
2
|
+
export declare const tddEvidenceTemplate = "---\nstatus: missing\nstory: {{story}}\n---\n\n# TDD Evidence: {{story}}\n\n## Test Intent\n\n## Red\n- Command:\n- Result:\n\n## Green\n- Command:\n- Result:\n\n## Refactor\n- Command:\n- Result:\n";
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const storyTemplate = `---
|
|
2
|
+
status: draft
|
|
3
|
+
evidence: .speckit/evidence/{{slug}}-tdd-evidence.md
|
|
4
|
+
context: pending
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Story: {{title}}
|
|
8
|
+
|
|
9
|
+
## Intent
|
|
10
|
+
{{intent}}
|
|
11
|
+
|
|
12
|
+
## Acceptance Criteria
|
|
13
|
+
- Given ...
|
|
14
|
+
- When ...
|
|
15
|
+
- Then ...
|
|
16
|
+
|
|
17
|
+
## TDD Checklist
|
|
18
|
+
- [ ] Test targets identified
|
|
19
|
+
- [ ] Red evidence recorded
|
|
20
|
+
- [ ] Green evidence recorded
|
|
21
|
+
- [ ] Refactor validation recorded
|
|
22
|
+
|
|
23
|
+
## Notes
|
|
24
|
+
- Risks:
|
|
25
|
+
- Dependencies:
|
|
26
|
+
|
|
27
|
+
## Spec Anti-Mistake Checklist
|
|
28
|
+
- Reuse existing project patterns before adding new files.
|
|
29
|
+
- Verify file locations before editing.
|
|
30
|
+
- Do not introduce new libraries without explicit need.
|
|
31
|
+
- Preserve existing behavior unless an acceptance criterion requires change.
|
|
32
|
+
- Capture previous-story learnings if this continues prior work.
|
|
33
|
+
`;
|
|
34
|
+
export const tddEvidenceTemplate = `---
|
|
35
|
+
status: missing
|
|
36
|
+
story: {{story}}
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
# TDD Evidence: {{story}}
|
|
40
|
+
|
|
41
|
+
## Test Intent
|
|
42
|
+
|
|
43
|
+
## Red
|
|
44
|
+
- Command:
|
|
45
|
+
- Result:
|
|
46
|
+
|
|
47
|
+
## Green
|
|
48
|
+
- Command:
|
|
49
|
+
- Result:
|
|
50
|
+
|
|
51
|
+
## Refactor
|
|
52
|
+
- Command:
|
|
53
|
+
- Result:
|
|
54
|
+
`;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const workflowContract: {
|
|
2
|
+
readonly phases: readonly ["start", "memory", "sprint", "context", "sync", "triage", "ready", "run", "checkpoint", "review", "close"];
|
|
3
|
+
readonly skills: string[];
|
|
4
|
+
readonly statuses: readonly ["DONE", "DONE_WITH_CONCERNS", "BLOCKED", "NEEDS_CONTEXT"];
|
|
5
|
+
readonly requiredPromptTerms: readonly [readonly ["project memory", ".speckit/memory/project-context.md"], readonly ["active session", ".speckit/sessions/active.md"], readonly ["current context", ".speckit/context/current.md"], readonly ["subagent handoff", ".speckit/context/subagent-handoff.md"], readonly ["acceptance criteria", "AC coverage", "ACs"], readonly ["red-green-refactor", "red/green/refactor"], readonly ["checkpoint", "speckit session checkpoint"], readonly ["compact", "compaction", "speckit session compact"], readonly ["robot-safe", "bv --robot", "robot commands"], readonly ["ready-for-dev", "speckit ready"]];
|
|
6
|
+
readonly requiredRouterTerms: readonly ["smallest matching Speckit skill", "Work context path", "Reports path", "Plans path", "Hydrate runtime tasks", "Sync completed runtime tasks"];
|
|
7
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { specSkillNames } from "./skill-catalog.js";
|
|
2
|
+
export const workflowContract = {
|
|
3
|
+
phases: [
|
|
4
|
+
"start",
|
|
5
|
+
"memory",
|
|
6
|
+
"sprint",
|
|
7
|
+
"context",
|
|
8
|
+
"sync",
|
|
9
|
+
"triage",
|
|
10
|
+
"ready",
|
|
11
|
+
"run",
|
|
12
|
+
"checkpoint",
|
|
13
|
+
"review",
|
|
14
|
+
"close",
|
|
15
|
+
],
|
|
16
|
+
skills: specSkillNames(),
|
|
17
|
+
statuses: ["DONE", "DONE_WITH_CONCERNS", "BLOCKED", "NEEDS_CONTEXT"],
|
|
18
|
+
requiredPromptTerms: [
|
|
19
|
+
["project memory", ".speckit/memory/project-context.md"],
|
|
20
|
+
["active session", ".speckit/sessions/active.md"],
|
|
21
|
+
["current context", ".speckit/context/current.md"],
|
|
22
|
+
["subagent handoff", ".speckit/context/subagent-handoff.md"],
|
|
23
|
+
["acceptance criteria", "AC coverage", "ACs"],
|
|
24
|
+
["red-green-refactor", "red/green/refactor"],
|
|
25
|
+
["checkpoint", "speckit session checkpoint"],
|
|
26
|
+
["compact", "compaction", "speckit session compact"],
|
|
27
|
+
["robot-safe", "bv --robot", "robot commands"],
|
|
28
|
+
["ready-for-dev", "speckit ready"],
|
|
29
|
+
],
|
|
30
|
+
requiredRouterTerms: [
|
|
31
|
+
"smallest matching Speckit skill",
|
|
32
|
+
"Work context path",
|
|
33
|
+
"Reports path",
|
|
34
|
+
"Plans path",
|
|
35
|
+
"Hydrate runtime tasks",
|
|
36
|
+
"Sync completed runtime tasks",
|
|
37
|
+
],
|
|
38
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getAdapters } from "../config/adapter-registry.js";
|
|
4
|
+
import { validatePermissionPolicy } from "./permission-auditor.js";
|
|
5
|
+
import { workflowContract } from "./workflow-contract.js";
|
|
6
|
+
export async function validateWorkflowContract(root) {
|
|
7
|
+
const checks = [];
|
|
8
|
+
const catalog = await read(root, ".speckit/skills/catalog.md");
|
|
9
|
+
const router = await read(root, ".speckit/agents/super-agent.md");
|
|
10
|
+
const flow = await read(root, ".speckit/flows/spec-flow.md");
|
|
11
|
+
const runPrompt = await read(root, ".speckit/prompts/spec-run.md");
|
|
12
|
+
checks.push(requiredFile(".speckit/skills/catalog.md", catalog));
|
|
13
|
+
checks.push(requiredFile(".speckit/agents/super-agent.md", router));
|
|
14
|
+
checks.push(requiredFile(".speckit/flows/spec-flow.md", flow));
|
|
15
|
+
checks.push(requiredFile(".speckit/prompts/spec-run.md", runPrompt));
|
|
16
|
+
checks.push(await skillsCheck(root, catalog));
|
|
17
|
+
checks.push(containsAll("super-agent router", router, workflowContract.requiredRouterTerms));
|
|
18
|
+
checks.push(orderedFlowCheck(flow));
|
|
19
|
+
checks.push(containsAll("enterprise run prompt", runPrompt, workflowContract.requiredPromptTerms));
|
|
20
|
+
checks.push(await permissionPolicyCheck(root));
|
|
21
|
+
checks.push(await adapterPromptCheck(root));
|
|
22
|
+
return checks;
|
|
23
|
+
}
|
|
24
|
+
async function permissionPolicyCheck(root) {
|
|
25
|
+
const missing = await validatePermissionPolicy(root);
|
|
26
|
+
return {
|
|
27
|
+
name: "permission policy",
|
|
28
|
+
ok: missing.length === 0,
|
|
29
|
+
detail: missing.length === 0 ? "aligned" : `missing: ${missing.join(", ")}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function requiredFile(path, content) {
|
|
33
|
+
return {
|
|
34
|
+
name: path,
|
|
35
|
+
ok: content !== undefined,
|
|
36
|
+
detail: content === undefined ? "missing" : "present",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async function skillsCheck(root, catalog) {
|
|
40
|
+
const missing = [];
|
|
41
|
+
for (const skill of workflowContract.skills) {
|
|
42
|
+
if (!(await exists(root, `.speckit/skills/${skill}.md`)) || !catalog?.includes(skill)) {
|
|
43
|
+
missing.push(skill);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
name: "core skills",
|
|
48
|
+
ok: missing.length === 0,
|
|
49
|
+
detail: missing.length === 0 ? `${workflowContract.skills.length} skills aligned` : `missing: ${missing.join(", ")}`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function orderedFlowCheck(flow) {
|
|
53
|
+
if (!flow)
|
|
54
|
+
return { name: "workflow phase order", ok: false, detail: "flow file missing" };
|
|
55
|
+
let cursor = -1;
|
|
56
|
+
const missing = [];
|
|
57
|
+
for (const phase of workflowContract.phases) {
|
|
58
|
+
const next = flow.indexOf(phase, cursor + 1);
|
|
59
|
+
if (next === -1) {
|
|
60
|
+
missing.push(phase);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
cursor = next;
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
name: "workflow phase order",
|
|
67
|
+
ok: missing.length === 0,
|
|
68
|
+
detail: missing.length === 0 ? workflowContract.phases.join(" -> ") : `missing or out of order: ${missing.join(", ")}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function containsAll(name, content, terms) {
|
|
72
|
+
const haystack = content?.toLowerCase() ?? "";
|
|
73
|
+
const missing = terms.filter((term) => !matchesTerm(haystack, term)).map(termLabel);
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
ok: missing.length === 0,
|
|
77
|
+
detail: missing.length === 0 ? "aligned" : `missing: ${missing.join(", ")}`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function matchesTerm(haystack, term) {
|
|
81
|
+
const alternatives = Array.isArray(term) ? term : [term];
|
|
82
|
+
return alternatives.some((alternative) => haystack.includes(alternative.toLowerCase()));
|
|
83
|
+
}
|
|
84
|
+
function termLabel(term) {
|
|
85
|
+
return typeof term === "string" ? term : term[0];
|
|
86
|
+
}
|
|
87
|
+
async function adapterPromptCheck(root) {
|
|
88
|
+
const missing = [];
|
|
89
|
+
const checked = [];
|
|
90
|
+
for (const adapter of getAdapters("all")) {
|
|
91
|
+
const content = await readExisting(root, adapter.outputPaths);
|
|
92
|
+
if (!content)
|
|
93
|
+
continue;
|
|
94
|
+
checked.push(adapter.name);
|
|
95
|
+
const check = containsAll(adapter.name, content, workflowContract.requiredPromptTerms);
|
|
96
|
+
if (!check.ok)
|
|
97
|
+
missing.push(`${adapter.name} (${check.detail})`);
|
|
98
|
+
}
|
|
99
|
+
if (checked.length === 0) {
|
|
100
|
+
return { name: "adapter prompt contracts", ok: false, detail: "no adapter files found" };
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
name: "adapter prompt contracts",
|
|
104
|
+
ok: missing.length === 0,
|
|
105
|
+
detail: missing.length === 0 ? `aligned: ${checked.join(", ")}` : missing.join("; "),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async function readExisting(root, paths) {
|
|
109
|
+
const chunks = [];
|
|
110
|
+
for (const path of paths) {
|
|
111
|
+
const content = await read(root, path);
|
|
112
|
+
if (content)
|
|
113
|
+
chunks.push(content);
|
|
114
|
+
}
|
|
115
|
+
return chunks.length > 0 ? chunks.join("\n") : undefined;
|
|
116
|
+
}
|
|
117
|
+
async function read(root, path) {
|
|
118
|
+
try {
|
|
119
|
+
return await readFile(join(root, path), "utf8");
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function exists(root, path) {
|
|
126
|
+
try {
|
|
127
|
+
await access(join(root, path));
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -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.3.1`.
|
|
8
8
|
|
|
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.
|
|
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/session tests plus CI.
|
|
10
10
|
|
|
11
11
|
## Milestones
|
|
12
12
|
|
|
@@ -15,17 +15,21 @@ The CLI is npx-ready, generates Agile + TDD rules, creates workflow artifacts, s
|
|
|
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
17
|
| Workflow engine | Complete | `start`, `shape`, `plan`, `context`, `quick`, `sync`, `triage`, `ready`, `run`, `review`, and `close` generate linked artifacts. |
|
|
18
|
+
| Long session manager | Complete | `memory refresh` plus `session start/checkpoint/compact/resume/status` preserve agent continuity outside chat history. |
|
|
18
19
|
| IDE adapters | Complete | Claude Code, Codex, Antigravity, OpenCode, Cursor. |
|
|
19
|
-
| Beads integration | MVP Complete | `next`
|
|
20
|
+
| Beads integration | MVP Complete | `next` and `graph triage/plan/insights` wrap BV robot mode; `sync` exports story metadata JSONL. |
|
|
21
|
+
| Sprint automation | MVP Complete | `sprint plan` and `sprint next` select work from synced stories. |
|
|
20
22
|
| Enterprise harness | MVP Complete | `init --enterprise` creates flow, tool-policy, and prompt harness files; `doctor --deep` verifies them. |
|
|
23
|
+
| Curated skill catalog | Complete | Speckit now generates focused phase skills, a schema, delegation statuses, and task hydration/sync-back guidance. |
|
|
24
|
+
| Workflow contract validator | Complete | `speckit validate` and `doctor --deep` check phase order, core skills, router terms, run prompt terms, and adapter parity. |
|
|
25
|
+
| Permission policy | MVP Complete | `.speckit/permissions.yaml` and `permissions audit` cover privacy files, heavy paths, destructive commands, and release commands. |
|
|
21
26
|
| Validation | Complete | Build and `node:test` suite pass locally with flow contract gates. |
|
|
22
27
|
| GitHub publish | Complete | Initial code pushed to `trieungoctam/speckit` at commit `7e5c582`; status docs follow-up in progress. |
|
|
23
28
|
| npm package readiness | Ready | Package scoped as `@trieungoctam/speckit`; `npm pack --dry-run` passes. |
|
|
24
29
|
|
|
25
30
|
## Next Roadmap
|
|
26
31
|
|
|
27
|
-
- Expand Speckit Enterprise Harness with richer profile and context-pack layers.
|
|
28
32
|
- Add `review --deep` with blind, edge-case, and acceptance audit prompts.
|
|
29
|
-
- Add graph
|
|
33
|
+
- Add graph drift and history correlation commands.
|
|
30
34
|
- Add GitHub trusted publishing for npm releases.
|
|
31
35
|
- Add snapshot tests for full adapter file contents.
|