@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,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
|
@@ -2,6 +2,7 @@ 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
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");
|
|
@@ -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,17 @@
|
|
|
1
|
+
import { validateWorkflowContract } from "../core/workflow-validator.js";
|
|
2
|
+
export async function validateCommand(options) {
|
|
3
|
+
const stdout = options.stdout ?? console;
|
|
4
|
+
const checks = await validateWorkflowContract(options.root);
|
|
5
|
+
const status = checks.every((check) => check.ok) ? "ok" : "needs-attention";
|
|
6
|
+
const report = { status, checks };
|
|
7
|
+
if (options.json) {
|
|
8
|
+
stdout.log(JSON.stringify(report, null, 2));
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
stdout.log(`Speckit workflow contract: ${status}`);
|
|
12
|
+
for (const check of checks) {
|
|
13
|
+
stdout.log(`${check.ok ? "ok" : "fail"} ${check.name}: ${check.detail}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return status === "ok" ? 0 : 1;
|
|
17
|
+
}
|
|
@@ -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,10 @@
|
|
|
1
|
+
export type PermissionAuditInput = {
|
|
2
|
+
path?: string;
|
|
3
|
+
command?: string;
|
|
4
|
+
};
|
|
5
|
+
export type PermissionAuditResult = {
|
|
6
|
+
status: "allow" | "ask" | "deny";
|
|
7
|
+
reasons: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function auditPermission(input: PermissionAuditInput): PermissionAuditResult;
|
|
10
|
+
export declare function validatePermissionPolicy(root: string): Promise<string[]>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { destructiveCommands, heavyPatterns, releaseCommands, sensitivePatterns } from "./permission-policy.js";
|
|
4
|
+
const safeSecretSuffixes = [".example", ".sample", ".template"];
|
|
5
|
+
export function auditPermission(input) {
|
|
6
|
+
const reasons = [];
|
|
7
|
+
if (input.path) {
|
|
8
|
+
if (isSensitivePath(input.path))
|
|
9
|
+
reasons.push(`privacy:${input.path}`);
|
|
10
|
+
if (isHeavyPath(input.path))
|
|
11
|
+
reasons.push(`scout:${input.path}`);
|
|
12
|
+
}
|
|
13
|
+
if (input.command) {
|
|
14
|
+
if (matchesAny(input.command, destructiveCommands))
|
|
15
|
+
reasons.push(`destructive:${input.command}`);
|
|
16
|
+
if (matchesAny(input.command, releaseCommands)) {
|
|
17
|
+
return { status: reasons.length > 0 ? "deny" : "ask", reasons: [`release:${input.command}`, ...reasons] };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return reasons.length > 0 ? { status: "deny", reasons } : { status: "allow", reasons };
|
|
21
|
+
}
|
|
22
|
+
export async function validatePermissionPolicy(root) {
|
|
23
|
+
const content = await read(join(root, ".speckit/permissions.yaml"));
|
|
24
|
+
const missing = [];
|
|
25
|
+
if (!content)
|
|
26
|
+
return ["missing .speckit/permissions.yaml"];
|
|
27
|
+
for (const token of ["precedence:", "privacy:", "scout:", "destructive:", "release:", "file_write: ask"]) {
|
|
28
|
+
if (!content.includes(token))
|
|
29
|
+
missing.push(token);
|
|
30
|
+
}
|
|
31
|
+
for (const pattern of [".env", "node_modules", "git reset --hard*", "npm publish*"]) {
|
|
32
|
+
if (!content.includes(pattern))
|
|
33
|
+
missing.push(pattern);
|
|
34
|
+
}
|
|
35
|
+
return missing;
|
|
36
|
+
}
|
|
37
|
+
function isSensitivePath(path) {
|
|
38
|
+
const clean = normalize(path);
|
|
39
|
+
const name = basename(clean);
|
|
40
|
+
if (safeSecretSuffixes.some((suffix) => name.endsWith(suffix)))
|
|
41
|
+
return false;
|
|
42
|
+
return sensitivePatterns.some((pattern) => matchGlob(clean, pattern));
|
|
43
|
+
}
|
|
44
|
+
function isHeavyPath(path) {
|
|
45
|
+
const parts = normalize(path).split("/");
|
|
46
|
+
return heavyPatterns.some((pattern) => parts.includes(pattern.replace(/"/g, "")));
|
|
47
|
+
}
|
|
48
|
+
function matchesAny(command, patterns) {
|
|
49
|
+
return patterns.some((pattern) => matchGlob(command.trim(), pattern));
|
|
50
|
+
}
|
|
51
|
+
function matchGlob(value, pattern) {
|
|
52
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".*").replace(/\*/g, ".*");
|
|
53
|
+
return new RegExp(`^${escaped}$`).test(value) || new RegExp(escaped).test(value);
|
|
54
|
+
}
|
|
55
|
+
function normalize(value) {
|
|
56
|
+
return value.replace(/\\/g, "/");
|
|
57
|
+
}
|
|
58
|
+
async function read(path) {
|
|
59
|
+
try {
|
|
60
|
+
return await readFile(path, "utf8");
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ManagedFile } from "./managed-files.js";
|
|
2
|
+
export declare const sensitivePatterns: string[];
|
|
3
|
+
export declare const heavyPatterns: string[];
|
|
4
|
+
export declare const destructiveCommands: string[];
|
|
5
|
+
export declare const releaseCommands: string[];
|
|
6
|
+
export declare function permissionPolicyFile(): ManagedFile;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { text } from "./managed-files.js";
|
|
2
|
+
export const sensitivePatterns = [
|
|
3
|
+
".env",
|
|
4
|
+
".env.*",
|
|
5
|
+
"**/.env",
|
|
6
|
+
"**/.env.*",
|
|
7
|
+
"**/credentials*",
|
|
8
|
+
"**/secret.yml",
|
|
9
|
+
"**/secrets.yml",
|
|
10
|
+
"**/secret.yaml",
|
|
11
|
+
"**/secrets.yaml",
|
|
12
|
+
"**/*.pem",
|
|
13
|
+
"**/*.key",
|
|
14
|
+
"**/id_rsa",
|
|
15
|
+
"**/id_ed25519",
|
|
16
|
+
];
|
|
17
|
+
export const heavyPatterns = [
|
|
18
|
+
"node_modules",
|
|
19
|
+
"dist",
|
|
20
|
+
"build",
|
|
21
|
+
".next",
|
|
22
|
+
".nuxt",
|
|
23
|
+
"__pycache__",
|
|
24
|
+
".venv",
|
|
25
|
+
"venv",
|
|
26
|
+
"vendor",
|
|
27
|
+
"target",
|
|
28
|
+
".git",
|
|
29
|
+
"coverage",
|
|
30
|
+
];
|
|
31
|
+
export const destructiveCommands = [
|
|
32
|
+
"rm -rf*",
|
|
33
|
+
"git reset --hard*",
|
|
34
|
+
"git clean -fd*",
|
|
35
|
+
"git push --force*",
|
|
36
|
+
"sudo *",
|
|
37
|
+
"chmod -R*",
|
|
38
|
+
"chown -R*",
|
|
39
|
+
];
|
|
40
|
+
export const releaseCommands = ["git push*", "npm publish*", "* deploy*", "vercel --prod*", "fly deploy*"];
|
|
41
|
+
export function permissionPolicyFile() {
|
|
42
|
+
return {
|
|
43
|
+
path: ".speckit/permissions.yaml",
|
|
44
|
+
content: text(`version: 1
|
|
45
|
+
mode: enterprise
|
|
46
|
+
precedence:
|
|
47
|
+
- deny
|
|
48
|
+
- ask
|
|
49
|
+
- allow
|
|
50
|
+
defaults:
|
|
51
|
+
file_read: allow_workspace
|
|
52
|
+
file_write: ask
|
|
53
|
+
shell: ask
|
|
54
|
+
network: ask
|
|
55
|
+
external_directory: deny
|
|
56
|
+
guards:
|
|
57
|
+
privacy:
|
|
58
|
+
action: deny
|
|
59
|
+
allow_examples: true
|
|
60
|
+
patterns:
|
|
61
|
+
${sensitivePatterns.map((pattern) => ` - "${pattern}"`).join("\n")}
|
|
62
|
+
scout:
|
|
63
|
+
action: deny
|
|
64
|
+
patterns:
|
|
65
|
+
${heavyPatterns.map((pattern) => ` - "${pattern}"`).join("\n")}
|
|
66
|
+
destructive:
|
|
67
|
+
action: deny
|
|
68
|
+
commands:
|
|
69
|
+
${destructiveCommands.map((command) => ` - "${command}"`).join("\n")}
|
|
70
|
+
release:
|
|
71
|
+
action: ask
|
|
72
|
+
commands:
|
|
73
|
+
${releaseCommands.map((command) => ` - "${command}"`).join("\n")}
|
|
74
|
+
phases:
|
|
75
|
+
shape:
|
|
76
|
+
file_write:
|
|
77
|
+
- ".speckit/specs/**"
|
|
78
|
+
plan:
|
|
79
|
+
file_write:
|
|
80
|
+
- ".speckit/plans/**"
|
|
81
|
+
- ".speckit/stories/**"
|
|
82
|
+
- ".speckit/evidence/**"
|
|
83
|
+
review:
|
|
84
|
+
file_write: deny
|
|
85
|
+
shell_allow:
|
|
86
|
+
- "git diff*"
|
|
87
|
+
- "git log*"
|
|
88
|
+
- "npm test*"
|
|
89
|
+
ship:
|
|
90
|
+
shell_ask:
|
|
91
|
+
${releaseCommands.map((command) => ` - "${command}"`).join("\n")}`),
|
|
92
|
+
};
|
|
93
|
+
}
|
package/dist/core/scaffold.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { markdown, text } from "./managed-files.js";
|
|
2
2
|
import { agilePolicy, enterpriseSafetyPolicy, tddPolicy, workflowReview, workflowShape, workflowTddRun, } from "./policy.js";
|
|
3
|
+
import { permissionPolicyFile } from "./permission-policy.js";
|
|
4
|
+
import { storyTemplate, tddEvidenceTemplate } from "./templates.js";
|
|
3
5
|
export function coreFiles() {
|
|
4
6
|
return [
|
|
5
7
|
{
|
|
@@ -13,6 +15,14 @@ tdd:
|
|
|
13
15
|
task_graph:
|
|
14
16
|
provider: beads
|
|
15
17
|
bv_robot_only: true
|
|
18
|
+
memory:
|
|
19
|
+
project_context: .speckit/memory/project-context.md
|
|
20
|
+
session_dir: .speckit/sessions
|
|
21
|
+
skills:
|
|
22
|
+
catalog: .speckit/skills/catalog.md
|
|
23
|
+
super_agent: .speckit/agents/super-agent.md
|
|
24
|
+
sprint:
|
|
25
|
+
status: .speckit/sprint/status.yaml
|
|
16
26
|
adapters:
|
|
17
27
|
enabled: []`),
|
|
18
28
|
},
|
|
@@ -22,68 +32,17 @@ adapters:
|
|
|
22
32
|
path: ".speckit/rules/enterprise-safety.md",
|
|
23
33
|
content: markdown(enterpriseSafetyPolicy),
|
|
24
34
|
},
|
|
35
|
+
permissionPolicyFile(),
|
|
25
36
|
{ path: ".speckit/workflows/shape.md", content: markdown(workflowShape) },
|
|
26
37
|
{ path: ".speckit/workflows/tdd-run.md", content: markdown(workflowTddRun) },
|
|
27
38
|
{ path: ".speckit/workflows/review.md", content: markdown(workflowReview) },
|
|
28
39
|
{
|
|
29
40
|
path: ".speckit/templates/story.md",
|
|
30
|
-
content: markdown(
|
|
31
|
-
status: draft
|
|
32
|
-
evidence: .speckit/evidence/{{slug}}-tdd-evidence.md
|
|
33
|
-
context: pending
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
# Story: {{title}}
|
|
37
|
-
|
|
38
|
-
## Intent
|
|
39
|
-
{{intent}}
|
|
40
|
-
|
|
41
|
-
## Acceptance Criteria
|
|
42
|
-
- Given ...
|
|
43
|
-
- When ...
|
|
44
|
-
- Then ...
|
|
45
|
-
|
|
46
|
-
## TDD Checklist
|
|
47
|
-
- [ ] Test targets identified
|
|
48
|
-
- [ ] Red evidence recorded
|
|
49
|
-
- [ ] Green evidence recorded
|
|
50
|
-
- [ ] Refactor validation recorded
|
|
51
|
-
|
|
52
|
-
## Notes
|
|
53
|
-
- Risks:
|
|
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.
|
|
62
|
-
`),
|
|
41
|
+
content: markdown(storyTemplate),
|
|
63
42
|
},
|
|
64
43
|
{
|
|
65
44
|
path: ".speckit/templates/tdd-evidence.md",
|
|
66
|
-
content: markdown(
|
|
67
|
-
status: missing
|
|
68
|
-
story: {{story}}
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
# TDD Evidence: {{story}}
|
|
72
|
-
|
|
73
|
-
## Test Intent
|
|
74
|
-
|
|
75
|
-
## Red
|
|
76
|
-
- Command:
|
|
77
|
-
- Result:
|
|
78
|
-
|
|
79
|
-
## Green
|
|
80
|
-
- Command:
|
|
81
|
-
- Result:
|
|
82
|
-
|
|
83
|
-
## Refactor
|
|
84
|
-
- Command:
|
|
85
|
-
- Result:
|
|
86
|
-
`),
|
|
45
|
+
content: markdown(tddEvidenceTemplate),
|
|
87
46
|
},
|
|
88
47
|
];
|
|
89
48
|
}
|
|
@@ -94,12 +53,14 @@ export function enterpriseFiles() {
|
|
|
94
53
|
content: markdown(`# Spec Flow
|
|
95
54
|
|
|
96
55
|
## Order
|
|
97
|
-
start ->
|
|
56
|
+
start -> memory -> sprint -> context -> sync -> triage -> ready -> run -> checkpoint -> review -> close
|
|
98
57
|
|
|
99
58
|
## Traceability
|
|
100
59
|
- A session links the original idea to every downstream artifact.
|
|
60
|
+
- Project memory links durable rules to every new session.
|
|
101
61
|
- A story links acceptance criteria to TDD evidence.
|
|
102
62
|
- Sync links stories to graph-ready JSONL.
|
|
63
|
+
- Session checkpoints preserve file changes, decisions, and next steps across compaction.
|
|
103
64
|
- Close links review output back to story and graph sync.
|
|
104
65
|
`),
|
|
105
66
|
},
|
|
@@ -127,6 +88,11 @@ graph:
|
|
|
127
88
|
|
|
128
89
|
- Current context: \`.speckit/context/current.md\`
|
|
129
90
|
- Subagent handoff: \`.speckit/context/subagent-handoff.md\`
|
|
91
|
+
- Project memory: \`.speckit/memory/project-context.md\`
|
|
92
|
+
- Active session summary: \`.speckit/sessions/active.md\` and the linked \`summary.md\`
|
|
93
|
+
- Active artifact log: linked \`artifact-log.md\`
|
|
94
|
+
- Super agent router: \`.speckit/agents/super-agent.md\`
|
|
95
|
+
- Skill catalog: \`.speckit/skills/catalog.md\`
|
|
130
96
|
- Story with acceptance criteria
|
|
131
97
|
- Matching TDD evidence file
|
|
132
98
|
- Tool policy: \`.speckit/tool-policy.yaml\`
|
|
@@ -136,6 +102,7 @@ graph:
|
|
|
136
102
|
- Story status is \`ready-for-dev\`.
|
|
137
103
|
- Context status is \`fresh\`.
|
|
138
104
|
- \`speckit ready <story>\` returns ready.
|
|
105
|
+
- \`speckit session status\` identifies the active session.
|
|
139
106
|
|
|
140
107
|
## Allowed Edits
|
|
141
108
|
|
|
@@ -149,26 +116,31 @@ graph:
|
|
|
149
116
|
Use the red-green-refactor loop.
|
|
150
117
|
|
|
151
118
|
1. Read the current context before touching code.
|
|
152
|
-
2.
|
|
153
|
-
3.
|
|
154
|
-
4.
|
|
155
|
-
5.
|
|
156
|
-
6.
|
|
157
|
-
7.
|
|
158
|
-
8.
|
|
159
|
-
9.
|
|
119
|
+
2. Read the super agent router and select the smallest matching Speckit skill.
|
|
120
|
+
3. Confirm the story path, acceptance criteria, and evidence path.
|
|
121
|
+
4. Write or run the smallest failing test first.
|
|
122
|
+
5. Record red evidence before implementation.
|
|
123
|
+
6. Implement the smallest change that satisfies the failing test.
|
|
124
|
+
7. Record green evidence after tests pass.
|
|
125
|
+
8. Refactor only while tests stay green, then record validation.
|
|
126
|
+
9. Use graph commands only through robot-safe flags such as \`bv --robot-next --format json\`.
|
|
127
|
+
10. Run \`speckit session checkpoint --note "<phase complete>"\` after red, green, refactor, and review boundaries.
|
|
128
|
+
11. Run \`speckit session compact\` before context gets noisy or before handing off to another agent.
|
|
129
|
+
12. Run \`speckit review\` before handoff.
|
|
160
130
|
|
|
161
131
|
## Stop Conditions
|
|
162
132
|
|
|
163
133
|
- Stop if acceptance criteria are missing.
|
|
164
134
|
- Stop if the evidence file cannot be identified.
|
|
165
135
|
- Stop if \`speckit ready <story>\` reports blocked.
|
|
136
|
+
- Stop if active session state cannot be written.
|
|
166
137
|
- Stop before destructive commands, production changes, or secret access.
|
|
167
138
|
|
|
168
139
|
## Completion Signal
|
|
169
140
|
|
|
170
141
|
- All acceptance criteria satisfied.
|
|
171
142
|
- TDD evidence status can be advanced through red, green, and refactor.
|
|
143
|
+
- Session checkpoint and compact summary are current.
|
|
172
144
|
- Review handoff is ready.
|
|
173
145
|
`),
|
|
174
146
|
},
|
|
@@ -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>>;
|