@slowcook-ai/cli 0.2.0 → 0.4.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.
Files changed (40) hide show
  1. package/README.md +65 -1
  2. package/dist/cli.js +14 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/init/index.d.ts +2 -0
  5. package/dist/commands/init/index.d.ts.map +1 -0
  6. package/dist/commands/init/index.js +181 -0
  7. package/dist/commands/init/index.js.map +1 -0
  8. package/dist/commands/init/plan.d.ts +58 -0
  9. package/dist/commands/init/plan.d.ts.map +1 -0
  10. package/dist/commands/init/plan.js +136 -0
  11. package/dist/commands/init/plan.js.map +1 -0
  12. package/dist/commands/init/templates.d.ts +17 -0
  13. package/dist/commands/init/templates.d.ts.map +1 -0
  14. package/dist/commands/init/templates.js +163 -0
  15. package/dist/commands/init/templates.js.map +1 -0
  16. package/dist/commands/refine/agent.d.ts +62 -0
  17. package/dist/commands/refine/agent.d.ts.map +1 -0
  18. package/dist/commands/refine/agent.js +209 -0
  19. package/dist/commands/refine/agent.js.map +1 -0
  20. package/dist/commands/refine/index.d.ts +2 -0
  21. package/dist/commands/refine/index.d.ts.map +1 -0
  22. package/dist/commands/refine/index.js +177 -0
  23. package/dist/commands/refine/index.js.map +1 -0
  24. package/dist/commands/refine/llm.d.ts +27 -0
  25. package/dist/commands/refine/llm.d.ts.map +1 -0
  26. package/dist/commands/refine/llm.js +33 -0
  27. package/dist/commands/refine/llm.js.map +1 -0
  28. package/dist/commands/refine/prompts.d.ts +23 -0
  29. package/dist/commands/refine/prompts.d.ts.map +1 -0
  30. package/dist/commands/refine/prompts.js +115 -0
  31. package/dist/commands/refine/prompts.js.map +1 -0
  32. package/dist/commands/refine/relationship.d.ts +21 -0
  33. package/dist/commands/refine/relationship.d.ts.map +1 -0
  34. package/dist/commands/refine/relationship.js +148 -0
  35. package/dist/commands/refine/relationship.js.map +1 -0
  36. package/dist/commands/refine/spec-yaml.d.ts +192 -0
  37. package/dist/commands/refine/spec-yaml.d.ts.map +1 -0
  38. package/dist/commands/refine/spec-yaml.js +125 -0
  39. package/dist/commands/refine/spec-yaml.js.map +1 -0
  40. package/package.json +7 -3
@@ -0,0 +1,163 @@
1
+ // Static and parameterized file contents written by `slowcook init`.
2
+ // Version is bumped in lockstep with the CLI package.
3
+ export const CLI_VERSION_FOR_TEMPLATES = "0.3.0";
4
+ export const SLOWCOOK_CODEOWNERS_MARKER_BEGIN = "# --- slowcook:frozen-paths BEGIN ---";
5
+ export const SLOWCOOK_CODEOWNERS_MARKER_END = "# --- slowcook:frozen-paths END ---";
6
+ export function frozenPathsJson() {
7
+ return (JSON.stringify({
8
+ $schema: "./frozen-paths.schema.json",
9
+ $doc: "Paths frozen by slowcook. See https://github.com/aminazar/slowcook for the design. " +
10
+ "To modify any of these: either get CODEOWNERS approval, or add the 'override-freeze' label " +
11
+ "to the PR (guard runs in advisory mode, audit trail preserved).",
12
+ directories: [
13
+ "tests/",
14
+ "tests-fixtures/",
15
+ "tests-helpers/",
16
+ ".brewing/manifests/",
17
+ ],
18
+ files: [
19
+ "vitest.config.ts",
20
+ "vitest.config.mjs",
21
+ "vitest.config.js",
22
+ ".brewing/frozen-paths.json",
23
+ ".brewing/stack.json",
24
+ ".github/workflows/slowcook.yml",
25
+ ],
26
+ partial: {
27
+ "package.json": {
28
+ frozen_key_paths: ["scripts.test", "scripts.test:watch"],
29
+ },
30
+ },
31
+ }, null, 2) + "\n");
32
+ }
33
+ export function stackJson(params) {
34
+ const doc = "Project-level stack configuration consumed by slowcook (@slowcook-ai/stack-ts). " +
35
+ "Tells the harness how to discover and run tests. Only include suites that are " +
36
+ "actually runnable — slowcook refuses to record an incomplete manifest." +
37
+ (params.hasPlaywright
38
+ ? " (Playwright detected in package.json; slowcook's playwright discovery is not yet " +
39
+ "implemented, so the e2e suite is intentionally omitted. Add it back post-upgrade.)"
40
+ : "");
41
+ return (JSON.stringify({
42
+ $schema: "./stack.schema.json",
43
+ $doc: doc,
44
+ language: "typescript",
45
+ package_manager: "npm",
46
+ test: {
47
+ backend: {
48
+ runner: "vitest",
49
+ run_command: "npx vitest run",
50
+ discover_command: "npx vitest list",
51
+ reporter_format: "vitest-list-lines",
52
+ },
53
+ },
54
+ lint: {
55
+ lint_command: "npm run lint",
56
+ typecheck_command: "npm run typecheck",
57
+ },
58
+ }, null, 2) + "\n");
59
+ }
60
+ export function brewingReadme() {
61
+ return `# \`.brewing/\`
62
+
63
+ Consumer-side configuration for [slowcook](https://github.com/aminazar/slowcook), a TDD-first agentic development harness.
64
+
65
+ ## Contents
66
+
67
+ | Path | Purpose |
68
+ |---|---|
69
+ | \`frozen-paths.json\` | What's immutable during brewing (tests, configs, manifests) |
70
+ | \`stack.json\` | How slowcook invokes tests / coverage / lint for this project |
71
+ | \`manifests/\` | Per-story test manifests; populated by \`slowcook manifest record\` |
72
+
73
+ ## Running slowcook locally
74
+
75
+ \`\`\`bash
76
+ npx --yes @slowcook-ai/cli@latest guard --base origin/main --head HEAD
77
+ npx --yes @slowcook-ai/cli@latest manifest record
78
+ npx --yes @slowcook-ai/cli@latest manifest verify
79
+ \`\`\`
80
+
81
+ ## When you legitimately need to modify a frozen path
82
+
83
+ 1. Open a PR with the change.
84
+ 2. Add the \`override-freeze\` label to the PR.
85
+ 3. Guard runs in advisory mode (surfaces violations but doesn't fail).
86
+ 4. CODEOWNERS still requires explicit approval.
87
+ 5. Merge audit trail: PR number + \`override-freeze\` label + approval.
88
+
89
+ Deliberately slightly inconvenient. Frozen-path changes are rare events that deserve a reviewer's eyes.
90
+ `;
91
+ }
92
+ export function slowcookWorkflow(cliVersion) {
93
+ return `name: slowcook
94
+
95
+ on:
96
+ pull_request:
97
+ types: [opened, synchronize, reopened, labeled, unlabeled]
98
+
99
+ concurrency:
100
+ group: slowcook-\${{ github.event.pull_request.number }}
101
+ cancel-in-progress: true
102
+
103
+ # Pin CLI version for reproducibility; bump deliberately via a PR.
104
+ env:
105
+ SLOWCOOK_CLI: "@slowcook-ai/cli@${cliVersion}"
106
+
107
+ jobs:
108
+ check:
109
+ name: slowcook checks
110
+ runs-on: ubuntu-latest
111
+ steps:
112
+ - uses: actions/checkout@v4
113
+ with:
114
+ fetch-depth: 0
115
+
116
+ - uses: actions/setup-node@v4
117
+ with:
118
+ node-version: 20
119
+
120
+ - name: Guard — frozen paths
121
+ env:
122
+ HAS_OVERRIDE: \${{ contains(github.event.pull_request.labels.*.name, 'override-freeze') }}
123
+ run: |
124
+ set -eu
125
+ ARGS="--base origin/\${{ github.base_ref }} --head HEAD"
126
+ if [ "$HAS_OVERRIDE" = "true" ]; then
127
+ ARGS="$ARGS --override"
128
+ echo "::notice::'override-freeze' label present — guard runs in advisory mode."
129
+ fi
130
+ npx --yes "$SLOWCOOK_CLI" guard $ARGS
131
+
132
+ - name: Manifest — verify discoverable tests
133
+ run: npx --yes "$SLOWCOOK_CLI" manifest verify
134
+ `;
135
+ }
136
+ export function codeownersSection(params) {
137
+ return `${SLOWCOOK_CODEOWNERS_MARKER_BEGIN}
138
+ # Paths frozen by slowcook. Agent-authored PRs cannot modify them;
139
+ # human edits must be reviewed. See https://github.com/aminazar/slowcook.
140
+
141
+ /tests/ ${params.owner}
142
+ /tests-fixtures/ ${params.owner}
143
+ /tests-helpers/ ${params.owner}
144
+ /vitest.config.* ${params.owner}
145
+ /.brewing/ ${params.owner}
146
+ /.github/workflows/slowcook.yml ${params.owner}
147
+ /CODEOWNERS ${params.owner}
148
+ ${SLOWCOOK_CODEOWNERS_MARKER_END}
149
+ `;
150
+ }
151
+ export function codeownersFullFile(params) {
152
+ // For repos that don't have CODEOWNERS yet — prepend a short header.
153
+ return `# CODEOWNERS
154
+ #
155
+ # Generated by \`slowcook init\`. The slowcook-managed section is between
156
+ # the marker comments; edit outside those markers freely.
157
+
158
+ ${codeownersSection(params)}`;
159
+ }
160
+ export function gitkeep() {
161
+ return "";
162
+ }
163
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/commands/init/templates.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,sDAAsD;AAEtD,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,CAAC;AASjD,MAAM,CAAC,MAAM,gCAAgC,GAAG,uCAAuC,CAAC;AACxF,MAAM,CAAC,MAAM,8BAA8B,GAAG,qCAAqC,CAAC;AAEpF,MAAM,UAAU,eAAe;IAC7B,OAAO,CACL,IAAI,CAAC,SAAS,CACZ;QACE,OAAO,EAAE,4BAA4B;QACrC,IAAI,EACF,qFAAqF;YACrF,6FAA6F;YAC7F,iEAAiE;QACnE,WAAW,EAAE;YACX,QAAQ;YACR,iBAAiB;YACjB,gBAAgB;YAChB,qBAAqB;SACtB;QACD,KAAK,EAAE;YACL,kBAAkB;YAClB,mBAAmB;YACnB,kBAAkB;YAClB,4BAA4B;YAC5B,qBAAqB;YACrB,gCAAgC;SACjC;QACD,OAAO,EAAE;YACP,cAAc,EAAE;gBACd,gBAAgB,EAAE,CAAC,cAAc,EAAE,oBAAoB,CAAC;aACzD;SACF;KACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAsB;IAC9C,MAAM,GAAG,GACP,kFAAkF;QAClF,gFAAgF;QAChF,wEAAwE;QACxE,CAAC,MAAM,CAAC,aAAa;YACnB,CAAC,CAAC,oFAAoF;gBACpF,oFAAoF;YACtF,CAAC,CAAC,EAAE,CAAC,CAAC;IAEV,OAAO,CACL,IAAI,CAAC,SAAS,CACZ;QACE,OAAO,EAAE,qBAAqB;QAC9B,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,YAAY;QACtB,eAAe,EAAE,KAAK;QACtB,IAAI,EAAE;YACJ,OAAO,EAAE;gBACP,MAAM,EAAE,QAAQ;gBAChB,WAAW,EAAE,gBAAgB;gBAC7B,gBAAgB,EAAE,iBAAiB;gBACnC,eAAe,EAAE,mBAAmB;aACrC;SACF;QACD,IAAI,EAAE;YACJ,YAAY,EAAE,cAAc;YAC5B,iBAAiB,EAAE,mBAAmB;SACvC;KACF,EACD,IAAI,EACJ,CAAC,CACF,GAAG,IAAI,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,OAAO;;;;;;;;;;;;oCAY2B,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6B7C,CAAC;AACF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAsB;IACtD,OAAO,GAAG,gCAAgC;;;;kCAIV,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;kCACZ,MAAM,CAAC,KAAK;EAC5C,8BAA8B;CAC/B,CAAC;AACF,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAsB;IACvD,qEAAqE;IACrE,OAAO;;;;;EAKP,iBAAiB,CAAC,MAAM,CAAC,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,62 @@
1
+ import type { LlmClient } from "./llm.js";
2
+ import type { ForgeAdapter, Spec } from "@slowcook-ai/core";
3
+ export declare const LABEL_CHANGE_OF_MIND = "change-of-mind";
4
+ export declare const LABEL_BLOCKED_CONTRADICTION = "blocked-contradiction";
5
+ export declare const LABEL_BLOCKED_OVERLAP = "blocked-overlap";
6
+ export declare const LABEL_SPEC_READY = "spec-ready";
7
+ export declare const LABEL_NEEDS_REFINEMENT = "needs-refinement";
8
+ export interface RefineContext {
9
+ issueNumber: number;
10
+ repoRoot: string;
11
+ forge: ForgeAdapter;
12
+ llm: LlmClient;
13
+ /** Model id for refinement (heavy reasoning). */
14
+ refineModel: string;
15
+ /** Model id for relationship analysis (cheaper). */
16
+ relationshipModel: string;
17
+ /** slowcook CLI version string for the spec's refined_by field. */
18
+ cliVersion: string;
19
+ /** Base branch for PRs (default: "main"). */
20
+ baseBranch: string;
21
+ /** Current UTC time (injectable for tests). */
22
+ now: Date;
23
+ }
24
+ export type RefineOutcome = {
25
+ kind: "questions-posted";
26
+ commentId: number;
27
+ } | {
28
+ kind: "spec-emitted";
29
+ specPath: string;
30
+ prUrl: string;
31
+ prNumber: number;
32
+ } | {
33
+ kind: "overlap-flagged";
34
+ conflicting_ids: string[];
35
+ } | {
36
+ kind: "contradiction-blocked";
37
+ conflicting_ids: string[];
38
+ } | {
39
+ kind: "change-of-mind-accepted";
40
+ supersedes: string[];
41
+ } | {
42
+ kind: "noop";
43
+ reason: string;
44
+ };
45
+ export declare function runRefinement(ctx: RefineContext): Promise<RefineOutcome>;
46
+ interface ParseContext {
47
+ storyId: string;
48
+ issueNumber: number;
49
+ createdAt: string;
50
+ cliVersion: string;
51
+ supersedes: string[];
52
+ }
53
+ export type AgentOutput = {
54
+ kind: "questions";
55
+ markdown: string;
56
+ } | {
57
+ kind: "spec";
58
+ spec: Spec;
59
+ };
60
+ export declare function parseAgentOutput(raw: string, ctx: ParseContext): AgentOutput;
61
+ export {};
62
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../../src/commands/refine/agent.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,UAAU,CAAC;AACtD,OAAO,KAAK,EACV,YAAY,EAGZ,IAAI,EAEL,MAAM,mBAAmB,CAAC;AAsB3B,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AACrD,eAAO,MAAM,2BAA2B,0BAA0B,CAAC;AACnE,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AACvD,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAC7C,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,YAAY,CAAC;IACpB,GAAG,EAAE,SAAS,CAAC;IACf,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,GAAG,EAAE,IAAI,CAAC;CACX;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC3E;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,uBAAuB,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,yBAAyB,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAKrC,wBAAsB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAgH9E;AA2CD,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC;AA+BjC,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,GAChB,WAAW,CA6Cb"}
@@ -0,0 +1,209 @@
1
+ import YAML from "yaml";
2
+ import { z } from "zod";
3
+ import { REFINEMENT_ANALYST_SYSTEM, SPEC_CHECKLIST_MD, draftPrTitle, draftPrBody, } from "./prompts.js";
4
+ import { readIndex, writeIndex, writeSpec, listActiveSpecs, nextStoryId, entryFromSpec, } from "./spec-yaml.js";
5
+ import { analyzeRelationship, contradictionCommentBody, overlapCommentBody, } from "./relationship.js";
6
+ import { applySupersede } from "@slowcook-ai/core";
7
+ export const LABEL_CHANGE_OF_MIND = "change-of-mind";
8
+ export const LABEL_BLOCKED_CONTRADICTION = "blocked-contradiction";
9
+ export const LABEL_BLOCKED_OVERLAP = "blocked-overlap";
10
+ export const LABEL_SPEC_READY = "spec-ready";
11
+ export const LABEL_NEEDS_REFINEMENT = "needs-refinement";
12
+ /** Schema for the <sentinel> block the agent uses when it emits a spec. */
13
+ const SpecEmissionFenceStart = "---";
14
+ export async function runRefinement(ctx) {
15
+ const issue = await ctx.forge.getIssue(ctx.issueNumber);
16
+ if (issue.state === "closed") {
17
+ return { kind: "noop", reason: "issue is closed" };
18
+ }
19
+ if (!issue.labels.includes(LABEL_NEEDS_REFINEMENT)) {
20
+ return { kind: "noop", reason: `issue is not labeled ${LABEL_NEEDS_REFINEMENT}` };
21
+ }
22
+ // Step 1: relationship analysis
23
+ const existingSpecs = listActiveSpecs(ctx.repoRoot);
24
+ const verdict = await analyzeRelationship({ issueTitle: issue.title, issueBody: issue.body, activeSpecs: existingSpecs }, { llm: ctx.llm, model: ctx.relationshipModel });
25
+ const hasChangeOfMind = issue.labels.includes(LABEL_CHANGE_OF_MIND);
26
+ if (verdict.kind === "overlap") {
27
+ const comment = await ctx.forge.createIssueComment(ctx.issueNumber, overlapCommentBody(verdict));
28
+ await ctx.forge.addIssueLabels(ctx.issueNumber, [LABEL_BLOCKED_OVERLAP]);
29
+ return { kind: "overlap-flagged", conflicting_ids: verdict.conflicting_ids };
30
+ }
31
+ if (verdict.kind === "contradiction") {
32
+ if (!hasChangeOfMind) {
33
+ await ctx.forge.createIssueComment(ctx.issueNumber, contradictionCommentBody(verdict, false));
34
+ await ctx.forge.addIssueLabels(ctx.issueNumber, [LABEL_BLOCKED_CONTRADICTION]);
35
+ return { kind: "contradiction-blocked", conflicting_ids: verdict.conflicting_ids };
36
+ }
37
+ // Authorized change-of-mind: post an acknowledgment and proceed
38
+ await ctx.forge.createIssueComment(ctx.issueNumber, contradictionCommentBody(verdict, true));
39
+ }
40
+ const supersedes = verdict.kind === "contradiction" && hasChangeOfMind ? verdict.conflicting_ids : [];
41
+ // Step 2: refinement loop (single round — ask or emit based on full history)
42
+ const comments = await ctx.forge.listIssueComments(ctx.issueNumber);
43
+ const chat = buildChatHistory(issue, comments, supersedes);
44
+ const storyId = nextStoryId(ctx.repoRoot);
45
+ const agentResponse = await ctx.llm.complete({
46
+ system: REFINEMENT_ANALYST_SYSTEM(SPEC_CHECKLIST_MD),
47
+ cacheSystem: true,
48
+ model: ctx.refineModel,
49
+ messages: chat,
50
+ maxTokens: 4096,
51
+ temperature: 0.2,
52
+ });
53
+ const parsed = parseAgentOutput(agentResponse, {
54
+ storyId,
55
+ issueNumber: ctx.issueNumber,
56
+ createdAt: ctx.now.toISOString(),
57
+ cliVersion: ctx.cliVersion,
58
+ supersedes,
59
+ });
60
+ if (parsed.kind === "questions") {
61
+ const comment = await ctx.forge.createIssueComment(ctx.issueNumber, parsed.markdown);
62
+ return { kind: "questions-posted", commentId: comment.id };
63
+ }
64
+ // Spec emitted → write, update index, open draft PR
65
+ const spec = parsed.spec;
66
+ const specPath = writeSpec(ctx.repoRoot, spec);
67
+ const index = readIndex(ctx.repoRoot);
68
+ const updatedIndex = applySupersede(index, { id: spec.story_id, entry: entryFromSpec(spec) }, supersedes);
69
+ writeIndex(ctx.repoRoot, updatedIndex);
70
+ const branch = `slowcook/spec/story-${spec.story_id}`;
71
+ await ctx.forge.git.createBranch(branch);
72
+ await ctx.forge.git.stage(specPath);
73
+ await ctx.forge.git.stage(`specs/_index.yaml`);
74
+ await ctx.forge.git.commit(`slowcook: spec story-${spec.story_id} — ${spec.title}\n\nRefined from #${ctx.issueNumber}.`);
75
+ await ctx.forge.git.push(branch);
76
+ const pr = await ctx.forge.createPullRequest({
77
+ title: draftPrTitle(spec.story_id, spec.title),
78
+ body: draftPrBody({
79
+ storyId: spec.story_id,
80
+ issueNumber: ctx.issueNumber,
81
+ supersedes,
82
+ }),
83
+ head: branch,
84
+ base: ctx.baseBranch,
85
+ draft: true,
86
+ labels: ["slowcook-spec"],
87
+ });
88
+ await ctx.forge.addIssueLabels(ctx.issueNumber, [LABEL_SPEC_READY]);
89
+ await ctx.forge.removeIssueLabel(ctx.issueNumber, LABEL_NEEDS_REFINEMENT);
90
+ return { kind: "spec-emitted", specPath, prUrl: pr.url, prNumber: pr.number };
91
+ }
92
+ // ----- helpers -----
93
+ function buildChatHistory(issue, comments, supersedes) {
94
+ // First message: the issue body + metadata.
95
+ const issueBlock = `# Issue #${issue.number}: ${issue.title}
96
+
97
+ ${issue.body}`;
98
+ const messages = [{ role: "user", content: issueBlock }];
99
+ // Interleave prior comments: bot comments become assistant turns, PM comments become user turns.
100
+ // Skip the issue-level "overlap/contradiction" analysis acknowledgments by matching their headers.
101
+ for (const c of comments) {
102
+ const skip = c.body.startsWith("### slowcook · overlap detected") ||
103
+ c.body.startsWith("### slowcook · contradiction") ||
104
+ c.body.startsWith("### slowcook · change-of-mind authorized");
105
+ if (skip)
106
+ continue;
107
+ messages.push({
108
+ role: c.is_bot ? "assistant" : "user",
109
+ content: c.body,
110
+ });
111
+ }
112
+ if (supersedes.length > 0) {
113
+ messages.push({
114
+ role: "user",
115
+ content: `(slowcook system note: this spec is authorized to supersede ${supersedes
116
+ .map((id) => `story-${id}`)
117
+ .join(", ")}. Set the \`supersedes\` field accordingly.)`,
118
+ });
119
+ }
120
+ return messages;
121
+ }
122
+ const EmittedSpecSchema = z.object({
123
+ story_id: z.string().optional(),
124
+ title: z.string(),
125
+ status: z.string().optional(),
126
+ created_at: z.string().optional(),
127
+ supersedes: z.array(z.string()).optional(),
128
+ superseded_by: z.unknown().optional(),
129
+ token_budget_usd: z.number().optional(),
130
+ estimate: z.enum(["small", "medium", "large"]).optional(),
131
+ source_issue: z.string().optional(),
132
+ refined_by: z.string().optional(),
133
+ actors: z.array(z.object({ name: z.string(), notes: z.string().optional() })),
134
+ preconditions: z.array(z.string()),
135
+ invariants: z.array(z.string()),
136
+ api_contract: z.array(z.unknown()).optional(),
137
+ ui_behavior: z.record(z.string(), z.string()).optional(),
138
+ acceptance_scenarios: z.array(z.string()),
139
+ non_goals: z.array(z.string()),
140
+ related_specs: z
141
+ .array(z.object({
142
+ id: z.string(),
143
+ relationship: z.enum(["overlap", "related", "superseded"]),
144
+ note: z.string().optional(),
145
+ }))
146
+ .optional(),
147
+ });
148
+ export function parseAgentOutput(raw, ctx) {
149
+ const trimmed = raw.trim();
150
+ // Heuristic: spec starts with `---` or is wrapped in a ```yaml block.
151
+ const yamlBlock = extractYamlBlock(trimmed);
152
+ if (yamlBlock) {
153
+ const doc = YAML.parse(yamlBlock);
154
+ const parsed = EmittedSpecSchema.safeParse(doc);
155
+ if (!parsed.success) {
156
+ throw new Error(`Agent emitted a YAML-shaped response but it failed validation:\n${parsed.error.issues
157
+ .map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`)
158
+ .join("\n")}\n\nRaw YAML:\n${yamlBlock.slice(0, 500)}`);
159
+ }
160
+ const d = parsed.data;
161
+ const spec = {
162
+ $schema: "./spec.schema.json",
163
+ story_id: ctx.storyId,
164
+ title: d.title,
165
+ status: "active",
166
+ created_at: ctx.createdAt,
167
+ supersedes: ctx.supersedes,
168
+ superseded_by: null,
169
+ token_budget_usd: d.token_budget_usd,
170
+ estimate: d.estimate,
171
+ source_issue: `#${ctx.issueNumber}`,
172
+ refined_by: `slowcook-refine@${ctx.cliVersion}`,
173
+ actors: d.actors,
174
+ preconditions: d.preconditions,
175
+ invariants: d.invariants,
176
+ api_contract: d.api_contract,
177
+ ui_behavior: d.ui_behavior,
178
+ acceptance_scenarios: d.acceptance_scenarios,
179
+ non_goals: d.non_goals,
180
+ related_specs: d.related_specs,
181
+ };
182
+ return { kind: "spec", spec };
183
+ }
184
+ // Otherwise: treat as a question round (markdown).
185
+ if (trimmed.length === 0) {
186
+ throw new Error("Agent returned an empty response; expected questions or a spec.");
187
+ }
188
+ return { kind: "questions", markdown: trimmed };
189
+ }
190
+ function extractYamlBlock(s) {
191
+ // ```yaml ... ``` fenced
192
+ const fence = s.match(/```yaml\s*([\s\S]*?)```/);
193
+ if (fence && fence[1])
194
+ return fence[1].trim();
195
+ // Bare YAML starting with --- (document separator)
196
+ if (s.startsWith("---")) {
197
+ return s;
198
+ }
199
+ // Content that's just YAML without front-matter fence — detect heuristically.
200
+ // If it contains typical spec keys AND doesn't look like markdown, treat as YAML.
201
+ const looksLikeYaml = /(^|\n)title:\s/i.test(s) &&
202
+ /(^|\n)actors:\s*/i.test(s) &&
203
+ /(^|\n)acceptance_scenarios:\s*/i.test(s);
204
+ if (looksLikeYaml && !s.includes("```") && !s.includes("### ")) {
205
+ return s;
206
+ }
207
+ return null;
208
+ }
209
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/commands/refine/agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function refine(argv: string[], cliVersion: string): Promise<void>;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/refine/index.ts"],"names":[],"mappings":"AAgHA,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmF9E"}
@@ -0,0 +1,177 @@
1
+ import { execSync } from "node:child_process";
2
+ import { GitHubAdapter } from "@slowcook-ai/forge-github";
3
+ import { AnthropicClient } from "./llm.js";
4
+ import { runRefinement } from "./agent.js";
5
+ function parseArgs(argv) {
6
+ const args = {
7
+ issueNumber: 0,
8
+ repoRoot: process.cwd(),
9
+ baseBranch: "main",
10
+ refineModel: "claude-opus-4-7",
11
+ relationshipModel: "claude-sonnet-4-5",
12
+ };
13
+ for (let i = 0; i < argv.length; i++) {
14
+ const arg = argv[i];
15
+ const next = argv[i + 1];
16
+ if (arg === "--issue" && next) {
17
+ args.issueNumber = parseInt(next, 10);
18
+ i++;
19
+ }
20
+ else if (arg === "--cwd" && next) {
21
+ args.repoRoot = next;
22
+ i++;
23
+ }
24
+ else if (arg === "--owner" && next) {
25
+ args.owner = next;
26
+ i++;
27
+ }
28
+ else if (arg === "--repo" && next) {
29
+ args.repo = next;
30
+ i++;
31
+ }
32
+ else if (arg === "--base" && next) {
33
+ args.baseBranch = next;
34
+ i++;
35
+ }
36
+ else if (arg === "--refine-model" && next) {
37
+ args.refineModel = next;
38
+ i++;
39
+ }
40
+ else if (arg === "--relationship-model" && next) {
41
+ args.relationshipModel = next;
42
+ i++;
43
+ }
44
+ else if (arg === "--help" || arg === "-h") {
45
+ printHelp();
46
+ process.exit(0);
47
+ }
48
+ }
49
+ if (!args.issueNumber || isNaN(args.issueNumber)) {
50
+ console.error("Missing required --issue <number>");
51
+ printHelp();
52
+ process.exit(64);
53
+ }
54
+ return args;
55
+ }
56
+ function printHelp() {
57
+ console.log(`
58
+ slowcook refine — drive a GitHub issue toward a frozen spec
59
+
60
+ Each invocation is one round:
61
+ - Analyzes the issue's relationship to existing active specs.
62
+ - If overlap or contradiction (without 'change-of-mind' label): posts a
63
+ comment explaining how to proceed and exits.
64
+ - If no blocker: either asks clarifying questions (as a comment) or emits
65
+ the final spec + opens a draft PR.
66
+
67
+ Re-run on every new PM comment (typically via a GitHub Actions workflow
68
+ triggered by issue_comment events).
69
+
70
+ Usage:
71
+ slowcook refine --issue <number> [options]
72
+
73
+ Options:
74
+ --issue <number> Issue to refine (required)
75
+ --cwd <path> Repo working directory (default: current dir)
76
+ --owner <login> Repo owner (default: parsed from 'git remote get-url origin')
77
+ --repo <name> Repo name (default: parsed from 'git remote get-url origin')
78
+ --base <branch> Base branch for the spec PR (default: main)
79
+ --refine-model <id> Model for the refinement loop (default: claude-opus-4-7)
80
+ --relationship-model <id> Model for relationship analysis (default: claude-sonnet-4-5)
81
+ --help, -h Show this help
82
+
83
+ Environment:
84
+ ANTHROPIC_API_KEY (required) Anthropic API key
85
+ GITHUB_TOKEN (required) GitHub token with issues:write, contents:write, pull-requests:write
86
+
87
+ Exit codes:
88
+ 0 outcome was reached (questions posted, spec emitted, or noop)
89
+ 2 script error (bad args, missing env, network failure)
90
+ `);
91
+ }
92
+ function detectOwnerRepo(cwd) {
93
+ try {
94
+ const url = execSync("git remote get-url origin", {
95
+ cwd,
96
+ encoding: "utf8",
97
+ stdio: ["ignore", "pipe", "ignore"],
98
+ }).trim();
99
+ const m = url.match(/github\.com[:/]([^/]+)\/([^/.]+)(?:\.git)?$/);
100
+ if (m && m[1] && m[2])
101
+ return { owner: m[1], repo: m[2] };
102
+ }
103
+ catch {
104
+ // not a git repo / no origin — fine
105
+ }
106
+ return null;
107
+ }
108
+ export async function refine(argv, cliVersion) {
109
+ const args = parseArgs(argv);
110
+ const anthropicKey = process.env["ANTHROPIC_API_KEY"];
111
+ if (!anthropicKey) {
112
+ console.error("ANTHROPIC_API_KEY environment variable is not set.");
113
+ process.exit(2);
114
+ }
115
+ const githubToken = process.env["GITHUB_TOKEN"];
116
+ if (!githubToken) {
117
+ console.error("GITHUB_TOKEN environment variable is not set.");
118
+ process.exit(2);
119
+ }
120
+ let owner = args.owner;
121
+ let repo = args.repo;
122
+ if (!owner || !repo) {
123
+ const detected = detectOwnerRepo(args.repoRoot);
124
+ if (!detected) {
125
+ console.error("Could not detect owner/repo from git remote. Pass --owner and --repo explicitly.");
126
+ process.exit(2);
127
+ }
128
+ owner = owner ?? detected.owner;
129
+ repo = repo ?? detected.repo;
130
+ }
131
+ const forge = new GitHubAdapter({ owner, repo, token: githubToken });
132
+ const llm = new AnthropicClient(anthropicKey);
133
+ const ctx = {
134
+ issueNumber: args.issueNumber,
135
+ repoRoot: args.repoRoot,
136
+ forge,
137
+ llm,
138
+ refineModel: args.refineModel,
139
+ relationshipModel: args.relationshipModel,
140
+ cliVersion,
141
+ baseBranch: args.baseBranch,
142
+ now: new Date(),
143
+ };
144
+ console.log(`slowcook refine · issue #${args.issueNumber} on ${owner}/${repo} (refine model: ${args.refineModel})`);
145
+ try {
146
+ const outcome = await runRefinement(ctx);
147
+ switch (outcome.kind) {
148
+ case "questions-posted":
149
+ console.log(`Posted clarifying questions (comment ${outcome.commentId}). Awaiting PM reply.`);
150
+ break;
151
+ case "spec-emitted":
152
+ console.log(`Spec written: ${outcome.specPath}`);
153
+ console.log(`Draft PR: ${outcome.prUrl}`);
154
+ break;
155
+ case "overlap-flagged":
156
+ console.log(`Overlap with ${outcome.conflicting_ids.join(", ")}. Awaiting PM resolution.`);
157
+ break;
158
+ case "contradiction-blocked":
159
+ console.log(`Contradiction with ${outcome.conflicting_ids.join(", ")}. Blocked until 'change-of-mind' label applied.`);
160
+ break;
161
+ case "change-of-mind-accepted":
162
+ console.log(`Change-of-mind accepted; will supersede ${outcome.supersedes.join(", ")}.`);
163
+ break;
164
+ case "noop":
165
+ console.log(`Noop: ${outcome.reason}.`);
166
+ break;
167
+ }
168
+ }
169
+ catch (e) {
170
+ console.error(`Refinement failed: ${e.message}`);
171
+ if (process.env["SLOWCOOK_DEBUG"]) {
172
+ console.error(e);
173
+ }
174
+ process.exit(2);
175
+ }
176
+ }
177
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/refine/index.ts"],"names":[],"mappings":"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"}