@hadooppei/hwcode 0.1.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.
@@ -0,0 +1,99 @@
1
+ import { existsSync, realpathSync } from "node:fs";
2
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
3
+
4
+ export interface GuardEnvironment {
5
+ home?: string;
6
+ tmpdir?: string;
7
+ }
8
+
9
+ export interface ExternalPathReference {
10
+ raw: string;
11
+ resolved: string;
12
+ }
13
+
14
+ function realpathWithMissingTail(path: string): string {
15
+ const tail: string[] = [];
16
+ let cursor = resolve(path);
17
+
18
+ while (!existsSync(cursor)) {
19
+ const parent = dirname(cursor);
20
+ if (parent === cursor) return resolve(path);
21
+ tail.unshift(cursor.slice(parent.length + (parent.endsWith(sep) ? 0 : 1)));
22
+ cursor = parent;
23
+ }
24
+
25
+ return resolve(realpathSync(cursor), ...tail);
26
+ }
27
+
28
+ export function canonicalizeWorkspaceRoot(root: string): string {
29
+ return realpathWithMissingTail(root);
30
+ }
31
+
32
+ export function resolveToolPath(root: string, rawPath: string, home?: string): string {
33
+ const expanded = rawPath === "~" || rawPath.startsWith(`~${sep}`)
34
+ ? resolve(home ?? root, rawPath.slice(2))
35
+ : rawPath;
36
+ return realpathWithMissingTail(isAbsolute(expanded) ? expanded : resolve(root, expanded));
37
+ }
38
+
39
+ export function isPathInsideRoot(root: string, candidate: string): boolean {
40
+ const relation = relative(canonicalizeWorkspaceRoot(root), realpathWithMissingTail(candidate));
41
+ return relation === "" || (!relation.startsWith(`..${sep}`) && relation !== ".." && !isAbsolute(relation));
42
+ }
43
+
44
+ function stripTrailingShellPunctuation(value: string): string {
45
+ return value.replace(/[\]}),;]+$/g, "");
46
+ }
47
+
48
+ function addExternalReference(
49
+ result: Map<string, ExternalPathReference>,
50
+ root: string,
51
+ raw: string,
52
+ resolved: string,
53
+ ): void {
54
+ const cleanResolved = realpathWithMissingTail(stripTrailingShellPunctuation(resolved));
55
+ if (!isPathInsideRoot(root, cleanResolved)) {
56
+ result.set(`${raw}\0${cleanResolved}`, { raw, resolved: cleanResolved });
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Find paths that a shell command explicitly names outside the workflow root.
62
+ * This is intentionally conservative; it is an approval gate, not a shell sandbox.
63
+ */
64
+ export function findExternalPathReferences(
65
+ command: string,
66
+ root: string,
67
+ environment: GuardEnvironment = {},
68
+ ): ExternalPathReference[] {
69
+ const result = new Map<string, ExternalPathReference>();
70
+ const canonicalRoot = canonicalizeWorkspaceRoot(root);
71
+ const home = environment.home;
72
+ const tmpdir = environment.tmpdir ?? "/tmp";
73
+
74
+ for (const match of command.matchAll(/\$\{?HOME\}?((?:\/[\w.@%+=:,~-]+)*)/g)) {
75
+ if (home) addExternalReference(result, canonicalRoot, match[0], resolve(home, `.${match[1] ?? ""}`));
76
+ }
77
+ for (const match of command.matchAll(/\$\{?TMPDIR\}?((?:\/[\w.@%+=:,~-]+)*)/g)) {
78
+ addExternalReference(result, canonicalRoot, match[0], resolve(tmpdir, `.${match[1] ?? ""}`));
79
+ }
80
+ for (const match of command.matchAll(/(?:^|[\s"'`=(:,<>])(~(?:\/[\w.@%+=:,~-]+)*)/g)) {
81
+ if (home) addExternalReference(result, canonicalRoot, match[1], resolveToolPath(canonicalRoot, match[1], home));
82
+ }
83
+
84
+ for (const match of command.matchAll(/(?:^|[\s"'`=(:,<>])((?:\/(?!\/)[^\s"'`|&;]+))/g)) {
85
+ const raw = stripTrailingShellPunctuation(match[1]);
86
+ addExternalReference(result, canonicalRoot, raw, raw);
87
+ }
88
+
89
+ for (const match of command.matchAll(/(?:^|[\s"'`=(:,<>])((?:\.\.\/)+[^\s"'`|&;]*)/g)) {
90
+ const raw = stripTrailingShellPunctuation(match[1]);
91
+ addExternalReference(result, canonicalRoot, raw, resolve(canonicalRoot, raw));
92
+ }
93
+
94
+ if (/\bmktemp\b/.test(command)) {
95
+ addExternalReference(result, canonicalRoot, "mktemp(default)", tmpdir);
96
+ }
97
+
98
+ return [...result.values()];
99
+ }
@@ -0,0 +1,252 @@
1
+ import { realpathSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { isAbsolute, resolve } from "node:path";
4
+
5
+ export const WORKING_DIRECTORY_STATE_TYPE = "hwcode-working-directory";
6
+
7
+ interface SessionEntry {
8
+ type: string;
9
+ customType?: string;
10
+ data?: unknown;
11
+ }
12
+
13
+ interface SessionDirectorySource {
14
+ getCwd(): string;
15
+ getEntries(): readonly SessionEntry[];
16
+ getSessionId(): string;
17
+ }
18
+
19
+ export interface WorkingDirectoryState {
20
+ version: 1;
21
+ cwd: string;
22
+ previousCwd?: string;
23
+ }
24
+
25
+ export interface DirectoryChange {
26
+ argument: string;
27
+ remainder: string;
28
+ }
29
+
30
+ export interface ChainedDirectoryChange {
31
+ argument: string;
32
+ standalone: boolean;
33
+ }
34
+
35
+ const workingDirectories = new Map<string, WorkingDirectoryState>();
36
+
37
+ function isWorkingDirectoryState(value: unknown): value is WorkingDirectoryState {
38
+ if (!value || typeof value !== "object") return false;
39
+ const data = value as Record<string, unknown>;
40
+ return data.version === 1
41
+ && typeof data.cwd === "string"
42
+ && (data.previousCwd === undefined || typeof data.previousCwd === "string");
43
+ }
44
+
45
+ export function findPersistedWorkingDirectory(
46
+ entries: readonly SessionEntry[],
47
+ ): WorkingDirectoryState | undefined {
48
+ for (const entry of [...entries].reverse()) {
49
+ if (entry.type !== "custom" || entry.customType !== WORKING_DIRECTORY_STATE_TYPE) continue;
50
+ if (isWorkingDirectoryState(entry.data)) return entry.data;
51
+ }
52
+ return undefined;
53
+ }
54
+
55
+ export function canonicalizeDirectory(path: string): string {
56
+ const canonical = realpathSync(path);
57
+ if (!statSync(canonical).isDirectory()) throw new Error(`Not a directory: ${path}`);
58
+ return canonical;
59
+ }
60
+
61
+ function initialState(source: SessionDirectorySource): WorkingDirectoryState {
62
+ const persisted = findPersistedWorkingDirectory(source.getEntries());
63
+ if (persisted) return { ...persisted, cwd: canonicalizeDirectory(persisted.cwd) };
64
+ return { version: 1, cwd: canonicalizeDirectory(source.getCwd()) };
65
+ }
66
+
67
+ export function getWorkingDirectoryState(source: SessionDirectorySource): WorkingDirectoryState {
68
+ return workingDirectories.get(source.getSessionId()) ?? initialState(source);
69
+ }
70
+
71
+ export function getWorkingDirectory(source: SessionDirectorySource): string {
72
+ return getWorkingDirectoryState(source).cwd;
73
+ }
74
+
75
+ export function setWorkingDirectoryState(
76
+ source: SessionDirectorySource,
77
+ state: WorkingDirectoryState,
78
+ ): void {
79
+ workingDirectories.set(source.getSessionId(), state);
80
+ }
81
+
82
+ export function resetWorkingDirectoryState(source: SessionDirectorySource): WorkingDirectoryState {
83
+ const state = initialState(source);
84
+ setWorkingDirectoryState(source, state);
85
+ return state;
86
+ }
87
+
88
+ function expandHome(path: string, home: string): string {
89
+ if (path === "~" || path === "$HOME" || path === "${HOME}") return home;
90
+ if (path.startsWith("~/")) return resolve(home, path.slice(2));
91
+ if (path.startsWith("$HOME/")) return resolve(home, path.slice(6));
92
+ if (path.startsWith("${HOME}/")) return resolve(home, path.slice(8));
93
+ return path;
94
+ }
95
+
96
+ export function resolveDirectoryArgument(
97
+ base: string,
98
+ argument: string,
99
+ previousCwd?: string,
100
+ home = homedir(),
101
+ ): string {
102
+ if (argument === "-") {
103
+ if (!previousCwd) throw new Error("No previous working directory is available.");
104
+ return canonicalizeDirectory(previousCwd);
105
+ }
106
+ const expanded = expandHome(argument || home, home);
107
+ return canonicalizeDirectory(isAbsolute(expanded) ? expanded : resolve(base, expanded));
108
+ }
109
+
110
+ export function resolveWorkingPath(base: string, path: string, home = homedir()): string {
111
+ const expanded = expandHome(path, home);
112
+ return isAbsolute(expanded) ? expanded : resolve(base, expanded);
113
+ }
114
+
115
+ function readShellWord(command: string, start: number): { value: string; end: number } | undefined {
116
+ let value = "";
117
+ let quote: "'" | '"' | undefined;
118
+ let index = start;
119
+
120
+ for (; index < command.length; index += 1) {
121
+ const character = command[index];
122
+ if (quote) {
123
+ if (character === quote) {
124
+ quote = undefined;
125
+ continue;
126
+ }
127
+ if (character === "\\" && quote === '"' && index + 1 < command.length) {
128
+ index += 1;
129
+ value += command[index];
130
+ continue;
131
+ }
132
+ value += character;
133
+ continue;
134
+ }
135
+
136
+ if (character === "'" || character === '"') {
137
+ quote = character;
138
+ continue;
139
+ }
140
+ if (character === "\\" && index + 1 < command.length) {
141
+ index += 1;
142
+ value += command[index];
143
+ continue;
144
+ }
145
+ if (/\s/u.test(character) || character === ";" || character === "&") break;
146
+ if ("|<>`".includes(character) || character === "$" && command[index + 1] === "(") return undefined;
147
+ value += character;
148
+ }
149
+
150
+ if (quote || value.length === 0) return undefined;
151
+ return { value, end: index };
152
+ }
153
+
154
+ /** Parse a leading persistent `cd`, optionally followed by `&&` or `;`. */
155
+ export function parseLeadingDirectoryChange(command: string): DirectoryChange | undefined {
156
+ let index = 0;
157
+ while (/\s/u.test(command[index] ?? "")) index += 1;
158
+ if (command.slice(index, index + 2) !== "cd") return undefined;
159
+ const next = command[index + 2];
160
+ if (next && !/\s/u.test(next) && next !== ";" && command.slice(index + 2, index + 4) !== "&&") {
161
+ return undefined;
162
+ }
163
+ index += 2;
164
+ while (/\s/u.test(command[index] ?? "")) index += 1;
165
+ if (command.slice(index, index + 2) === "--") {
166
+ index += 2;
167
+ while (/\s/u.test(command[index] ?? "")) index += 1;
168
+ }
169
+
170
+ let argument = "";
171
+ if (index < command.length && command[index] !== ";" && command.slice(index, index + 2) !== "&&") {
172
+ const word = readShellWord(command, index);
173
+ if (!word) return undefined;
174
+ argument = word.value;
175
+ index = word.end;
176
+ }
177
+ while (/\s/u.test(command[index] ?? "")) index += 1;
178
+
179
+ if (index >= command.length) return { argument, remainder: "" };
180
+ if (command.slice(index, index + 2) === "&&") index += 2;
181
+ else if (command[index] === ";") index += 1;
182
+ else return undefined;
183
+
184
+ return { argument, remainder: command.slice(index).trimStart() };
185
+ }
186
+
187
+ /** Find a standalone `cd` segment in a top-level `&&` or `;` command chain. */
188
+ export function findChainedDirectoryChange(command: string): ChainedDirectoryChange | undefined {
189
+ const segments: string[] = [];
190
+ let segmentStart = 0;
191
+ let quote: "'" | '"' | undefined;
192
+ let escaped = false;
193
+ let nesting = 0;
194
+
195
+ for (let index = 0; index < command.length; index += 1) {
196
+ const character = command[index];
197
+ if (escaped) {
198
+ escaped = false;
199
+ continue;
200
+ }
201
+ if (character === "\\" && quote !== "'") {
202
+ escaped = true;
203
+ continue;
204
+ }
205
+ if (quote) {
206
+ if (character === quote) quote = undefined;
207
+ continue;
208
+ }
209
+ if (character === "'" || character === '"') {
210
+ quote = character;
211
+ continue;
212
+ }
213
+ if (character === "(" || character === "{") {
214
+ nesting += 1;
215
+ continue;
216
+ }
217
+ if (character === ")" || character === "}") {
218
+ nesting = Math.max(0, nesting - 1);
219
+ continue;
220
+ }
221
+ if (nesting > 0) continue;
222
+
223
+ const separatorLength = command.slice(index, index + 2) === "&&" ? 2 : character === ";" ? 1 : 0;
224
+ if (!separatorLength) continue;
225
+ segments.push(command.slice(segmentStart, index));
226
+ index += separatorLength - 1;
227
+ segmentStart = index + 1;
228
+ }
229
+ segments.push(command.slice(segmentStart));
230
+
231
+ for (const segment of segments) {
232
+ const parsed = parseLeadingDirectoryChange(segment);
233
+ if (parsed && !parsed.remainder) {
234
+ return { argument: parsed.argument, standalone: segments.length === 1 };
235
+ }
236
+ }
237
+ return undefined;
238
+ }
239
+
240
+ export function getActiveWorkflowRoot(entries: readonly SessionEntry[]): string | undefined {
241
+ for (const entry of [...entries].reverse()) {
242
+ if (entry.type !== "custom" || entry.customType !== "hwcode-workflow-state") continue;
243
+ if (!entry.data || typeof entry.data !== "object") return undefined;
244
+ const data = entry.data as Record<string, unknown>;
245
+ return data.active === true && typeof data.root === "string" ? data.root : undefined;
246
+ }
247
+ return undefined;
248
+ }
249
+
250
+ export function shellQuote(value: string): string {
251
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
252
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "providers": [
3
+ {
4
+ "id": "local",
5
+ "name": "Qwen3 VL (8081)",
6
+ "baseUrl": "http://127.0.0.1:8081",
7
+ "apiKeyEnv": "PI_LOCAL_MODEL_API_KEY",
8
+ "models": [
9
+ {
10
+ "id": "qwen3-VL:2b",
11
+ "name": "Qwen3 VL 2B",
12
+ "input": ["text", "image"],
13
+ "contextWindow": 32768,
14
+ "maxTokens": 8192
15
+ }
16
+ ]
17
+ },
18
+ {
19
+ "id": "local-8080",
20
+ "name": "Qwen3.5 9B (8080)",
21
+ "baseUrl": "http://127.0.0.1:8080",
22
+ "apiKeyEnv": "PI_LOCAL_MODEL_API_KEY",
23
+ "models": [
24
+ {
25
+ "id": "Qwen3.5-9B-Q4_K_M",
26
+ "name": "Qwen3.5 9B Q4_K_M",
27
+ "input": ["text"],
28
+ "contextWindow": 32768,
29
+ "maxTokens": 8192
30
+ }
31
+ ]
32
+ },
33
+ {
34
+ "id": "hw",
35
+ "name": "hw",
36
+ "baseUrl": "http://127.0.0.1:8080/v1",
37
+ "apiKeyEnv": "HW_API_KEY",
38
+ "login": {
39
+ "enabled": true,
40
+ "promptBaseUrl": true,
41
+ "promptApiKey": true,
42
+ "apiKeyRequired": false,
43
+ "catalogPath": "models",
44
+ "timeoutMs": 15000
45
+ },
46
+ "modelDefaults": {
47
+ "input": ["text"],
48
+ "contextWindow": 32768,
49
+ "maxTokens": 8192
50
+ },
51
+ "models": [
52
+ {
53
+ "id": "Qwen3.5-9B-Q4_K_M",
54
+ "name": "Qwen3.5 9B Q4_K_M",
55
+ "input": ["text"],
56
+ "contextWindow": 32768,
57
+ "maxTokens": 8192
58
+ }
59
+ ]
60
+ }
61
+ ]
62
+ }
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: hwcode-sdd
3
+ description: Run a strict Git-root-locked spec-driven development workflow that inventories a repository, clarifies requirements, produces traceable specification artifacts, designs tests first, and implements with red-green-refactor. Use when the user invokes /hwcode-sdd or explicitly requests specification-led development.
4
+ ---
5
+
6
+ # HWCode SDD
7
+
8
+ Make the approved specification the source of truth. Move through discovery, specification, planning, tests, implementation, and convergence without skipping uncertainty.
9
+
10
+ ## Enforce activation and phase gates
11
+
12
+ Confirm that the activation message states a locked project root and Git status. If this skill was invoked directly without the `/hwcode-sdd` project command, do not begin work; ask the user to run `/hwcode-sdd [initial requirement]`. The command confirms the directory, enforces the path boundary, initializes Git when approved, and verifies that the current directory is the repository root.
13
+
14
+ Use the locked root as the only project workspace. Use in-root paths silently. Explain a genuine external-path need and rely on the one-call approval prompt; never bypass it.
15
+
16
+ Do not implement production behavior until the user has explicitly approved the requirements and then the design, test plan, and task plan. If later evidence exposes ambiguity, return to the earliest affected artifact and obtain approval again.
17
+
18
+ ## Establish a safe Git baseline
19
+
20
+ Inspect repository instructions, `.gitignore`, and the reported working tree before the codebase scan.
21
+
22
+ - If Git was just initialized, inspect all candidate files for credentials, local sessions, generated dependencies, build output, and machine-local configuration. Propose necessary ignore rules. Show the files proposed for staging and a baseline commit message, then obtain explicit approval before staging and committing.
23
+ - If an existing repository is dirty, summarize tracked and untracked changes without overwriting them. Offer a checkpoint commit, continuing with the dirty baseline, or stopping. Record the chosen baseline in the specification metadata.
24
+ - If the tree is clean, continue without creating a commit.
25
+
26
+ Never stage `.env` files, credentials, tokens, keys, local session data, dependency directories, or generated secrets. Never create a commit without explicit user approval.
27
+
28
+ ## Inventory the repository
29
+
30
+ Scan read-only before asking for the feature specification. Inspect only relevant levels of:
31
+
32
+ - project and agent instructions;
33
+ - manifests, entry points, top-level structure, and runtime configuration;
34
+ - business modules and principal data flows;
35
+ - routes, controllers, RPC/GraphQL contracts, schemas, and public interfaces;
36
+ - persistence, authentication, authorization, external services, and background jobs;
37
+ - containers, infrastructure, CI/CD, deployment configuration, and operational hooks;
38
+ - test layout, fixtures, quality commands, and coverage signals.
39
+
40
+ For a monorepo, summarize the whole repository at the top level and ask which application or package is in scope.
41
+
42
+ Report findings in this exact table shape, citing file paths as evidence:
43
+
44
+ | Area | Findings | Evidence |
45
+ |---|---|---|
46
+ | Purpose | ... | ... |
47
+ | Entry points and runtime | ... | ... |
48
+ | Business modules | ... | ... |
49
+ | Routes and APIs | ... | ... |
50
+ | Data and persistence | ... | ... |
51
+ | Integrations | ... | ... |
52
+ | Auth and permissions | ... | ... |
53
+ | Jobs and asynchronous work | ... | ... |
54
+ | Containers and deployment | ... | ... |
55
+ | Tests and tooling | ... | ... |
56
+ | Unknowns and risks | ... | ... |
57
+
58
+ State when an area is absent or not discoverable. Then ask what requirement the user wants to implement, while retaining any requirement supplied in the activation message.
59
+
60
+ ## Clarify and approve the specification
61
+
62
+ Separate required behavior and rationale from technical design. Check completeness across:
63
+
64
+ - actors, user journeys, triggers, inputs, outputs, and state changes;
65
+ - business rules, permissions, validation, failures, edge cases, and recovery;
66
+ - UI/API contracts, data ownership, compatibility, and migration;
67
+ - performance, reliability, security, privacy, accessibility, and observability;
68
+ - rollout, acceptance criteria, non-goals, and explicit out-of-scope behavior.
69
+
70
+ Do not invent a material product decision. Ask up to five of the most blocking questions per round in a table:
71
+
72
+ | ID | Question | Why it is needed | Options / recommendation |
73
+ |---|---|---|---|
74
+
75
+ Use repository evidence to recommend a default when possible. Continue QA rounds until no blocking ambiguity remains. If an expected test outcome is uncertain, treat that as an incomplete requirement and return to QA.
76
+
77
+ Create `.hwcode/specs/<requirement-slug>/requirements.md` using the template in [spec-artifacts.md](references/spec-artifacts.md). Assign stable `FR-xxx`, `NFR-xxx`, and `AC-xxx` identifiers. Present the completed draft and obtain explicit approval before design work.
78
+
79
+ ## Design and plan tests first
80
+
81
+ Read [spec-artifacts.md](references/spec-artifacts.md) and create, in order:
82
+
83
+ 1. `design.md` describing current behavior, the chosen architecture, contracts, data flow, errors, security, observability, migration, alternatives, and risks.
84
+ 2. `test-plan.md` mapping every acceptance criterion and applicable requirement to automated or explicitly justified manual verification.
85
+ 3. `tasks.md` containing dependency-aware tasks with exact target files where known. Put test and fixture tasks before their corresponding implementation tasks.
86
+
87
+ Maintain a traceability matrix from requirement to acceptance criterion, test, and implementation task. Analyze all four artifacts for contradictions, missing coverage, unresolved markers, and unsupported assumptions. Return upstream to repair gaps. Present the plan and obtain explicit approval before implementation.
88
+
89
+ ## Implement with red-green-refactor
90
+
91
+ Follow the repository's existing test patterns. If no suitable test framework exists, propose the smallest appropriate setup and obtain approval before adding dependencies.
92
+
93
+ For each behavior slice:
94
+
95
+ 1. Add or refine the specified test first.
96
+ 2. Run it and confirm it fails because the behavior is absent, not because the test is broken.
97
+ 3. Implement the minimum production change that satisfies the test and approved design.
98
+ 4. Run the focused test until green.
99
+ 5. Refactor without changing specified behavior.
100
+ 6. Run the broader relevant suite plus available type, lint, build, security, contract, or migration checks.
101
+ 7. Update task status and traceability evidence.
102
+
103
+ Require an explicit waiver, recorded in `requirements.md` and `test-plan.md`, for behavior that cannot reasonably begin with an automated failing test. Never weaken a test merely to make an implementation pass.
104
+
105
+ ## Converge before completion
106
+
107
+ Compare the approved requirements, design, tests, tasks, and final code. Resolve drift by changing the correct upstream artifact and re-approving material changes. Finish with:
108
+
109
+ - delivered behavior and non-goals;
110
+ - requirement-to-test-to-code traceability;
111
+ - commands run and results;
112
+ - migrations, operational considerations, residual risks, and follow-ups;
113
+ - Git status and a suggested checkpoint, without committing unless explicitly approved.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "HWCode SDD"
3
+ short_description: "Repository-aware spec-driven development workflow"
4
+ default_prompt: "Use $hwcode-sdd to analyze this repository and drive a tested feature from specification to implementation."
@@ -0,0 +1,161 @@
1
+ # HWCode SDD artifact templates
2
+
3
+ Keep all artifacts concise, evidence-based, and mutually consistent. Use stable identifiers after assignment; mark superseded requirements instead of silently renumbering them.
4
+
5
+ ## requirements.md
6
+
7
+ ```markdown
8
+ # <Requirement title>
9
+
10
+ ## Metadata
11
+ - Status: Draft | Approved | Superseded
12
+ - Owner: <user or team>
13
+ - Created / updated: <dates>
14
+ - Repository baseline: <commit or explicitly accepted dirty state>
15
+ - Scope: <application/package>
16
+
17
+ ## Problem and context
18
+ <Current behavior, affected users, and why this matters. Cite repository evidence.>
19
+
20
+ ## Goals
21
+ - G-001 ...
22
+
23
+ ## Non-goals
24
+ - NG-001 ...
25
+
26
+ ## Actors and scenarios
27
+ ### Scenario S-001: <name>
28
+ <Trigger, primary flow, state changes, and result.>
29
+
30
+ ## Functional requirements
31
+ - FR-001: The system must ...
32
+
33
+ ## Interfaces, API, and data
34
+ <Inputs, outputs, schemas, validation, compatibility, ownership, and migration.>
35
+
36
+ ## Non-functional requirements
37
+ - NFR-001: <measurable security, reliability, performance, privacy, accessibility, or observability constraint>
38
+
39
+ ## Acceptance criteria
40
+ - AC-001 (FR-001)
41
+ - Given ...
42
+ - When ...
43
+ - Then ...
44
+
45
+ ## Edge cases and failure behavior
46
+ - E-001 ...
47
+
48
+ ## Rollout and migration
49
+ <Feature flags, backward compatibility, data migration, rollback, and monitoring.>
50
+
51
+ ## Decisions
52
+ | ID | Decision | Rationale | Date |
53
+ |---|---|---|---|
54
+
55
+ ## Open questions
56
+ - None, or list blocking/non-blocking questions with owners.
57
+ ```
58
+
59
+ Approval gate: no blocking open question remains; behavior is testable; boundaries and non-goals are explicit; the user explicitly approves the document.
60
+
61
+ ## design.md
62
+
63
+ ```markdown
64
+ # Design: <Requirement title>
65
+
66
+ ## Requirements covered
67
+ <FR/NFR/AC identifiers>
68
+
69
+ ## Current behavior and constraints
70
+ <Relevant architecture and repository evidence.>
71
+
72
+ ## Chosen design
73
+ <Components, boundaries, responsibilities, and rationale.>
74
+
75
+ ## Control and data flow
76
+ <Key request/event sequences and state transitions.>
77
+
78
+ ## Contracts and data model
79
+ <API signatures, schemas, persistence, validation, and compatibility.>
80
+
81
+ ## Errors and recovery
82
+ <Failure semantics, retries, idempotency, rollback, and user feedback.>
83
+
84
+ ## Security, privacy, and observability
85
+ <Auth, authorization, secrets, sensitive data, logs, metrics, and alerts.>
86
+
87
+ ## Migration and rollout
88
+ <Ordering, compatibility window, flags, rollback, and cleanup.>
89
+
90
+ ## Alternatives rejected
91
+ | Alternative | Benefit | Reason rejected |
92
+ |---|---|---|
93
+
94
+ ## Risks and mitigations
95
+ | Risk | Likelihood / impact | Mitigation |
96
+ |---|---|---|
97
+ ```
98
+
99
+ Approval gate: every architectural decision traces to an approved requirement; contracts and failures are precise enough to test; the user explicitly approves the design.
100
+
101
+ ## test-plan.md
102
+
103
+ ```markdown
104
+ # Test plan: <Requirement title>
105
+
106
+ ## Strategy and scope
107
+ <Test levels, important boundaries, and exclusions.>
108
+
109
+ ## Environment and fixtures
110
+ <Deterministic setup, data, mocks/fakes, cleanup, and external-path needs.>
111
+
112
+ ## Traceability
113
+ | Test ID | Requirements / AC | Level | Scenario | Expected result | Planned file |
114
+ |---|---|---|---|---|---|
115
+ | T-001 | FR-001, AC-001 | Unit / integration / contract / E2E | ... | ... | ... |
116
+
117
+ ## Failure and boundary coverage
118
+ <Validation, permissions, concurrency, retries, timeouts, empty/large inputs, and regressions as applicable.>
119
+
120
+ ## Non-functional verification
121
+ <Security, performance, reliability, accessibility, privacy, and observability checks.>
122
+
123
+ ## Manual checks and approved waivers
124
+ <Why automation is impractical, exact procedure, expected result, approver, and date. Use "None" by default.>
125
+
126
+ ## Exit criteria
127
+ - Every applicable requirement and acceptance criterion has evidence.
128
+ - Focused and broader relevant checks pass.
129
+ - No unexplained flaky, skipped, weakened, or quarantined test remains.
130
+ ```
131
+
132
+ Approval gate: normal, error, edge, and regression cases are covered; uncertain expected results have returned to requirements QA; the user explicitly approves the plan.
133
+
134
+ ## tasks.md
135
+
136
+ ```markdown
137
+ # Tasks: <Requirement title>
138
+
139
+ ## Execution rules
140
+ - Respect dependencies and phase gates.
141
+ - For each behavior, execute test task before implementation task.
142
+ - Update status and evidence without deleting history.
143
+
144
+ ## Tasks
145
+ - [ ] TASK-001 [FR-001, T-001] Add failing test in `<path>`
146
+ - Depends on: none
147
+ - Red evidence: <pending>
148
+ - [ ] TASK-002 [FR-001, T-001] Implement behavior in `<path>`
149
+ - Depends on: TASK-001
150
+ - Green evidence: <pending>
151
+ - [ ] TASK-003 [FR-001] Refactor and run broader checks
152
+ - Depends on: TASK-002
153
+ - Evidence: <pending>
154
+
155
+ ## Final convergence
156
+ - [ ] All FR/NFR/AC identifiers map to tests and implementation evidence.
157
+ - [ ] Requirements, design, tests, tasks, and code contain no material drift.
158
+ - [ ] Migration, rollout, security, and operational work is complete or explicitly deferred.
159
+ ```
160
+
161
+ Approval gate: tasks are small and dependency-aware; test tasks precede production tasks; file targets and verification commands are concrete where discoverable; the user explicitly approves implementation.