@structured-vibe-coding/core 0.7.0 → 0.9.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,23 @@
1
+ # Constitution
2
+
3
+ <!-- Non-negotiables agents must not violate. Broader style lives in conventions.md. -->
4
+
5
+ ## Principles
6
+
7
+ -
8
+
9
+ ## Stack & tooling
10
+
11
+ -
12
+
13
+ ## Testing
14
+
15
+ -
16
+
17
+ ## Security
18
+
19
+ -
20
+
21
+ ## Process
22
+
23
+ -
@@ -6,7 +6,7 @@ Parent page for **stable** project docs. Humans and coding agents: start here, t
6
6
  |------|--------|----------|
7
7
  | **This map + memory** | `memory/*.md` | Long-lived |
8
8
  | **Active change** | `changes/<id>/` (see `.sdd/active-context.md`) | Until complete |
9
- | **Finished work** | `archive/<id>/` | History |
9
+ | **Finished work** | `changes/<id>/` with `status: completed` | History (pack stays in place; archive is opt-in) |
10
10
  | **Optional domains** | `domains/` | Long-lived by area |
11
11
 
12
12
  ## Stable docs
@@ -14,6 +14,7 @@ Parent page for **stable** project docs. Humans and coding agents: start here, t
14
14
  | Topic | File |
15
15
  |-------|------|
16
16
  | Product / vision | [product.md](product.md) |
17
+ | Constitution (non-negotiables) | [constitution.md](constitution.md) |
17
18
  | Architecture | [architecture.md](architecture.md) |
18
19
  | Coding & process conventions | [conventions.md](conventions.md) |
19
20
 
@@ -0,0 +1,67 @@
1
+ # Brainstorm
2
+
3
+ > **{{title}}** · `{{id}}` · stage `{{stage}}`
4
+ > **Optional stage** — skip if one approach is obvious:
5
+ > `sdd skip brainstorm -r "single approach is clear"`
6
+
7
+ ## Goal of exploration
8
+
9
+ -
10
+
11
+ ## Approach A
12
+
13
+ ### Idea
14
+
15
+ -
16
+
17
+ ### Pros
18
+
19
+ -
20
+
21
+ ### Cons / risks
22
+
23
+ -
24
+
25
+ ## Approach B
26
+
27
+ ### Idea
28
+
29
+ -
30
+
31
+ ### Pros
32
+
33
+ -
34
+
35
+ ### Cons / risks
36
+
37
+ -
38
+
39
+ ## Approach C (optional)
40
+
41
+ ### Idea
42
+
43
+ -
44
+
45
+ ### Pros
46
+
47
+ -
48
+
49
+ ### Cons / risks
50
+
51
+ -
52
+
53
+ ## Comparison
54
+
55
+ | Criterion | A | B | C |
56
+ |-----------|---|---|---|
57
+ | Complexity | | | |
58
+ | Risk | | | |
59
+ | Fit to constraints | | | |
60
+
61
+ ## Recommendation for design stage
62
+
63
+ -
64
+
65
+ ## Open questions (feed clarify / design)
66
+
67
+ -
@@ -0,0 +1,30 @@
1
+ # Clarify
2
+
3
+ > **{{title}}** · `{{id}}` · stage `{{stage}}`
4
+ > **Optional stage** — skip if nothing is ambiguous:
5
+ > `sdd skip {{stage}} -r "no open questions"`
6
+
7
+ ## Context
8
+
9
+ What just finished? What is about to start?
10
+
11
+ -
12
+
13
+ ## Clarifying questions
14
+
15
+ 1. **Q:**
16
+ **A:**
17
+
18
+ 2. **Q:**
19
+ **A:**
20
+
21
+ 3. **Q:**
22
+ **A:**
23
+
24
+ ## Decisions / constraints captured
25
+
26
+ -
27
+
28
+ ## Ready for next stage?
29
+
30
+ - [ ] No blocking unknowns remain (or explicitly accepted risks)
@@ -114,10 +114,7 @@ stages:
114
114
  title: Implementation
115
115
  summary: Execute tasks; respect ARB conditions
116
116
  skippable: false
117
- artifacts:
118
- - id: evidence
119
- path: evidence/
120
- required: false
117
+ artifacts: []
121
118
  gate:
122
119
  type: soft
123
120
  checklist:
@@ -142,9 +139,6 @@ stages:
142
139
  path: local-test-results.md
143
140
  template: local-test-results.md
144
141
  required: true
145
- - id: evidence
146
- path: evidence/local/
147
- required: false
148
142
  gate:
149
143
  type: hard
150
144
  overridable: true
@@ -154,8 +148,7 @@ stages:
154
148
  - "Failures fixed or filed before complete"
155
149
  verify:
156
150
  commands: []
157
- evidence_dir: evidence/local
158
151
 
159
152
  on_complete:
160
- archive: true
153
+ archive: false
161
154
  domain_sync: recommend
@@ -1,6 +1,6 @@
1
1
  name: feature
2
- description: Standard feature — intent, design, tasks, implement, local verify
3
- version: 1
2
+ description: "Feature — intent, optional clarify/brainstorm, design, tasks, implement, verify"
3
+ version: 2
4
4
  recommendation:
5
5
  priority: 30
6
6
  when:
@@ -10,6 +10,7 @@ stages:
10
10
  - id: intent
11
11
  title: Feature intent
12
12
  summary: Problem, users, scope, non-goals, success
13
+ kind: standard
13
14
  skippable: false
14
15
  artifacts:
15
16
  - id: feature
@@ -20,10 +21,55 @@ stages:
20
21
  type: soft
21
22
  checklist:
22
23
  - "Scope and non-goals are explicit"
24
+
25
+ # Optional: clarifying questions after intent
26
+ - id: clarify_intent
27
+ title: Clarify intent
28
+ summary: Ask clarifying questions before brainstorm/design (optional)
29
+ kind: clarify
30
+ optional: true
31
+ skippable: true
32
+ artifacts:
33
+ - id: clarify-intent
34
+ path: clarify-intent.md
35
+ template: clarify.md
36
+ required: false
37
+ gate:
38
+ type: soft
39
+ agent_context:
40
+ instructions: |
41
+ OPTIONAL clarify stage. If scope is already clear, tell the human to run:
42
+ sdd skip clarify_intent -r "intent is clear"
43
+ Otherwise ask focused clarifying questions (ambiguities, constraints, success metrics).
44
+ Write Q&A into clarify-intent.md. Do not design or implement yet.
45
+
46
+ # Optional: parallel approaches before design
47
+ - id: brainstorm
48
+ title: Brainstorm approaches
49
+ summary: Explore multiple approaches in parallel (optional)
50
+ kind: brainstorm
51
+ optional: true
52
+ skippable: true
53
+ artifacts:
54
+ - id: brainstorm
55
+ path: brainstorm.md
56
+ template: brainstorm.md
57
+ required: false
58
+ gate:
59
+ type: soft
60
+ agent_context:
61
+ instructions: |
62
+ OPTIONAL brainstorm stage. If a single approach is obvious, human may:
63
+ sdd skip brainstorm -r "single approach is clear"
64
+ Otherwise produce 2–4 approaches in brainstorm.md (trade-offs, risks).
65
+ Prefer parallel exploration: outline each approach independently, then compare.
66
+ Do not pick a final design until design stage. Do not implement code yet.
67
+
23
68
  - id: design
24
69
  title: Design
25
- summary: Approach, components, risks (keep short)
26
- skippable: true
70
+ summary: Chosen approach, components, risks (required for features)
71
+ kind: standard
72
+ skippable: false
27
73
  artifacts:
28
74
  - id: design
29
75
  path: design.md
@@ -31,9 +77,37 @@ stages:
31
77
  required: true
32
78
  gate:
33
79
  type: soft
80
+ checklist:
81
+ - "Approach chosen and risks noted"
82
+ agent_context:
83
+ instructions: |
84
+ Produce a concrete design in design.md based on feature.md and brainstorm.md (if any).
85
+ Do not leave template stubs empty — substantive content is required before sdd next.
86
+ Do not implement product code yet.
87
+
88
+ - id: clarify_design
89
+ title: Clarify design
90
+ summary: Clarifying questions after design (optional)
91
+ kind: clarify
92
+ optional: true
93
+ skippable: true
94
+ artifacts:
95
+ - id: clarify-design
96
+ path: clarify-design.md
97
+ template: clarify.md
98
+ required: false
99
+ gate:
100
+ type: soft
101
+ agent_context:
102
+ instructions: |
103
+ OPTIONAL. Skip if design is clear:
104
+ sdd skip clarify_design -r "design is clear"
105
+ Otherwise ask questions that unblock tasks/implement. Write answers to clarify-design.md.
106
+
34
107
  - id: tasks
35
108
  title: Tasks
36
109
  summary: Break work into implementable tasks
110
+ kind: standard
37
111
  skippable: true
38
112
  artifacts:
39
113
  - id: tasks
@@ -42,9 +116,34 @@ stages:
42
116
  required: true
43
117
  gate:
44
118
  type: soft
119
+ agent_context:
120
+ instructions: |
121
+ Break design into concrete tasks in tasks.md. Design must already be substantive.
122
+ Do not implement large product code yet.
123
+
124
+ - id: clarify_tasks
125
+ title: Clarify tasks
126
+ summary: Clarifying questions after tasks (optional)
127
+ kind: clarify
128
+ optional: true
129
+ skippable: true
130
+ artifacts:
131
+ - id: clarify-tasks
132
+ path: clarify-tasks.md
133
+ template: clarify.md
134
+ required: false
135
+ gate:
136
+ type: soft
137
+ agent_context:
138
+ instructions: |
139
+ OPTIONAL. Skip if tasks are clear:
140
+ sdd skip clarify_tasks -r "tasks are clear"
141
+ Otherwise resolve ambiguities before implement.
142
+
45
143
  - id: implement
46
144
  title: Implement
47
145
  summary: Build the feature locally
146
+ kind: standard
48
147
  skippable: false
49
148
  artifacts: []
50
149
  gate:
@@ -52,10 +151,12 @@ stages:
52
151
  agent_context:
53
152
  instructions: |
54
153
  Implement tasks from tasks.md following design.md and feature.md.
55
- Prefer incremental, testable steps.
154
+ Prefer incremental, testable steps. Honor memory/constitution.md if present.
155
+
56
156
  - id: local_verify
57
157
  title: Local verify
58
158
  summary: Verify the feature on this machine
159
+ kind: standard
59
160
  skippable: true
60
161
  artifacts:
61
162
  - id: local-test-plan
@@ -66,9 +167,6 @@ stages:
66
167
  path: local-test-results.md
67
168
  template: local-test-results.md
68
169
  required: true
69
- - id: evidence
70
- path: evidence/local/
71
- required: false
72
170
  gate:
73
171
  type: soft
74
172
  checklist:
@@ -76,7 +174,7 @@ stages:
76
174
  - "Key edge cases covered or waived with notes"
77
175
  verify:
78
176
  commands: []
79
- evidence_dir: evidence/local
177
+
80
178
  on_complete:
81
- archive: true
179
+ archive: false
82
180
  domain_sync: recommend
@@ -40,16 +40,12 @@ stages:
40
40
  path: local-test-results.md
41
41
  template: local-test-results.md
42
42
  required: false
43
- - id: evidence
44
- path: evidence/local/
45
- required: false
46
43
  gate:
47
44
  type: soft
48
45
  checklist:
49
46
  - "Smoke-checked locally"
50
47
  verify:
51
48
  commands: []
52
- evidence_dir: evidence/local
53
49
  on_complete:
54
- archive: true
50
+ archive: false
55
51
  domain_sync: never
@@ -55,9 +55,6 @@ stages:
55
55
  path: local-test-results.md
56
56
  template: local-test-results.md
57
57
  required: false
58
- - id: evidence
59
- path: evidence/local/
60
- required: false
61
58
  gate:
62
59
  type: soft
63
60
  checklist:
@@ -65,7 +62,6 @@ stages:
65
62
  - "No obvious regressions on touched paths"
66
63
  verify:
67
64
  commands: []
68
- evidence_dir: evidence/local
69
65
  on_complete:
70
- archive: true
66
+ archive: false
71
67
  domain_sync: never
@@ -46,5 +46,5 @@ stages:
46
46
  checklist:
47
47
  - "Clear recommendation recorded"
48
48
  on_complete:
49
- archive: true
49
+ archive: false
50
50
  domain_sync: never
@@ -19,7 +19,7 @@ import {
19
19
  removeOtherAgentHosts,
20
20
  renderThinAgent,
21
21
  writeAgentHandoff
22
- } from "./chunk-4ZDPIYEB.js";
22
+ } from "./chunk-M245MC6E.js";
23
23
  export {
24
24
  AGENT_INTEGRATIONS,
25
25
  AGENT_TARGET_OPTIONS,
@@ -60,6 +60,25 @@ async function copyDir(src, dest) {
60
60
  }
61
61
  }
62
62
  }
63
+ async function copyDirSkipExisting(src, dest) {
64
+ await ensureDir(dest);
65
+ const entries = await fs.readdir(src, { withFileTypes: true });
66
+ for (const entry of entries) {
67
+ const from = `${src}/${entry.name}`;
68
+ const to = `${dest}/${entry.name}`;
69
+ if (entry.isDirectory()) {
70
+ await copyDirSkipExisting(from, to);
71
+ } else if (!await pathExists(to)) {
72
+ await ensureDir(dirname(to));
73
+ await fs.copyFile(from, to);
74
+ }
75
+ }
76
+ }
77
+ async function writeTextIfMissing(path, content) {
78
+ if (await pathExists(path)) return false;
79
+ await writeText(path, content);
80
+ return true;
81
+ }
63
82
  async function removePath(path) {
64
83
  await fs.rm(path, { recursive: true, force: true });
65
84
  }
@@ -87,6 +106,14 @@ var StageSchema = z.object({
87
106
  id: z.string(),
88
107
  title: z.string().optional(),
89
108
  summary: z.string().optional(),
109
+ /**
110
+ * standard — normal work stage
111
+ * brainstorm — optional multi-approach exploration (before design)
112
+ * clarify — optional Q&A before leaving a major stage
113
+ */
114
+ kind: z.enum(["standard", "brainstorm", "clarify"]).default("standard"),
115
+ /** When true, stage is optional (skippable); agents may skip if not needed */
116
+ optional: z.boolean().default(false),
90
117
  artifacts: z.array(ArtifactDefSchema).default([]),
91
118
  gate: GateSchema.default({}),
92
119
  skippable: z.boolean().default(true),
@@ -117,7 +144,7 @@ var WorkflowSchema = z.object({
117
144
  priority: z.number().default(0)
118
145
  }).optional(),
119
146
  on_complete: z.object({
120
- archive: z.boolean().default(true),
147
+ archive: z.boolean().default(false),
121
148
  domain_sync: z.enum(["never", "recommend", "require"]).default("never")
122
149
  }).optional()
123
150
  });
@@ -137,7 +164,7 @@ var ConfigSchema = z.object({
137
164
  }).default({}),
138
165
  persistence: z.object({
139
166
  default: z.enum(["change_only", "domain_sync"]).default("change_only"),
140
- archive_on_complete: z.boolean().default(true),
167
+ archive_on_complete: z.boolean().default(false),
141
168
  domain_sync: z.object({
142
169
  mode: z.enum(["never", "recommend", "require"]).default("never"),
143
170
  anchored_domains: z.array(z.string()).default([])
@@ -588,6 +615,7 @@ async function buildAgentPrompt(ctx, config, projectRoot) {
588
615
  }
589
616
  parts.push(`## Constraints`);
590
617
  parts.push(`- Follow artifacts in the change directory as source of truth.`);
618
+ parts.push(`- Honor \`memory/constitution.md\` when present (non-negotiables).`);
591
619
  parts.push(`- Do not skip ARB/decision constraints if present.`);
592
620
  parts.push(`- Local development only \u2014 verify on this machine.`);
593
621
  parts.push(`- Prefer small, reviewable commits.`);
@@ -884,7 +912,8 @@ ${role.roleLine}
884
912
 
885
913
  1. \`.sdd/active-context.md\` \u2014 current change and stage
886
914
  2. \`.sdd/protocol.md\` \u2014 SDD rules for this repo (single playbook)
887
- 3. Active change files under \`changes/<id>/\` as listed in active-context
915
+ 3. \`memory/constitution.md\` if present \u2014 non-negotiables
916
+ 4. Active change files under \`changes/<id>/\` as listed in active-context
888
917
 
889
918
  ## Role rules
890
919
 
@@ -927,14 +956,15 @@ Also read: \`.sdd/active-context.md\`, \`.sdd/protocol.md\`, \`memory/constituti
927
956
  return outPath;
928
957
  }
929
958
  function agentKickoffMessage(opts) {
959
+ const event = opts.event ?? "context updated";
930
960
  return [
931
- `SDD: new change started.`,
961
+ `SDD: ${event}.`,
932
962
  `Title: ${opts.title}`,
933
963
  `Change: ${opts.changeId}`,
934
964
  `Stage: ${opts.stage}`,
935
965
  `Read in order: .sdd/active-context.md, .sdd/handoff.md, .sdd/protocol.md, memory/constitution.md (if present).`,
936
- `Fill/update current stage artifacts only under changes/${opts.changeId}/. Do not expand scope.`,
937
- `When the stage is done, tell the human to run: sdd next`
966
+ `Do the work for the current stage only under changes/${opts.changeId}/. Do not expand scope.`,
967
+ `When the stage is done, tell the human to run: sdd next (or sdd verify / sdd complete as appropriate).`
938
968
  ].join(" ");
939
969
  }
940
970
  async function refreshActiveAgentContext(projectRoot) {
@@ -991,6 +1021,7 @@ This repo uses **Structured Vibe Coding** (\`sdd\`). Agents must follow this fil
991
1021
  |------|--------|
992
1022
  | Process state | \`changes/<id>/meta.yaml\` (workflow, stage, gates) |
993
1023
  | Current task snapshot | \`.sdd/active-context.md\` |
1024
+ | Non-negotiables | \`memory/constitution.md\` if present |
994
1025
  | Stable product/tech rules | \`memory/*.md\` (start at \`memory/index.md\` if present) |
995
1026
  | Stage artifacts | files in \`changes/<id>/\` for the active change |
996
1027
 
@@ -998,8 +1029,9 @@ This repo uses **Structured Vibe Coding** (\`sdd\`). Agents must follow this fil
998
1029
 
999
1030
  1. \`.sdd/active-context.md\`
1000
1031
  2. This file (\`.sdd/protocol.md\`)
1001
- 3. \`meta.yaml\` + artifacts for the **current stage** (and prior stages if needed)
1002
- 4. \`memory/index.md\` (documentation map) if present, then linked memory pages when architecture or conventions matter
1032
+ 3. \`memory/constitution.md\` if present (non-negotiables agents must not violate)
1033
+ 4. \`meta.yaml\` + artifacts for the **current stage** (and prior stages if needed)
1034
+ 5. \`memory/index.md\` (documentation map) if present, then linked memory pages when architecture or conventions matter
1003
1035
 
1004
1036
  ## Stage behavior
1005
1037
 
@@ -1010,6 +1042,15 @@ This repo uses **Structured Vibe Coding** (\`sdd\`). Agents must follow this fil
1010
1042
  | tasks | Break work into implementable checklist |
1011
1043
  | implement | Code only for this change; follow tasks/acceptance |
1012
1044
  | local_verify | Check acceptance; fix only; prepare for complete |
1045
+ | brainstorm (optional) | Multiple approaches; parallel exploration; no final design yet |
1046
+ | clarify_* (optional) | Ask clarifying questions; write Q&A; or tell human to skip |
1047
+
1048
+ ## Optional stages
1049
+
1050
+ - Stages marked **optional** / kind \`brainstorm\` or \`clarify\` may be skipped:
1051
+ \`sdd skip <stage_id> -r "not needed"\`
1052
+ - Do **not** invent a skip by jumping stages. \`sdd next\` blocks on empty required stubs (e.g. design.md).
1053
+ - If the tool falls back to an earlier stage, **complete that stage** before advancing again.
1013
1054
 
1014
1055
  ## Hard rules
1015
1056
 
@@ -1018,6 +1059,7 @@ This repo uses **Structured Vibe Coding** (\`sdd\`). Agents must follow this fil
1018
1059
  3. **Honor constraints** in \`arb-decision.md\`, design, and memory non-negotiables.
1019
1060
  4. **Local verify** is part of done when the workflow has a verify stage: \`sdd verify\`.
1020
1061
  5. Prefer small, reviewable diffs.
1062
+ 6. **Required stage artifacts must be substantive** \u2014 not empty templates. Design cannot be skipped via empty design.md.
1021
1063
 
1022
1064
  ## Commands (human / shell)
1023
1065
 
@@ -1033,7 +1075,7 @@ sdd complete
1033
1075
  ## Out of scope for agents
1034
1076
 
1035
1077
  - Replacing CI/CD or team process tools
1036
- - Deleting \`.sdd\` / archive history
1078
+ - Deleting \`.sdd\` / completed change history
1037
1079
  - Claiming complete without verify when required
1038
1080
  `;
1039
1081
  function renderAgentsMd(integ) {
@@ -1047,6 +1089,7 @@ AI coding agents are **not** the same as IDEs: VS Code, Cursor, and IntelliJ hos
1047
1089
  |------------|--|
1048
1090
  | Live task | \`.sdd/active-context.md\` |
1049
1091
  | Playbook | \`.sdd/protocol.md\` |
1092
+ | Constitution | \`memory/constitution.md\` if present |
1050
1093
  | Doc map (stable) | \`memory/index.md\` if present |
1051
1094
 
1052
1095
  | AI agent | Files |
@@ -1086,6 +1129,8 @@ export {
1086
1129
  listDirs,
1087
1130
  listFiles,
1088
1131
  copyDir,
1132
+ copyDirSkipExisting,
1133
+ writeTextIfMissing,
1089
1134
  removePath,
1090
1135
  isInitialized,
1091
1136
  loadConfig,
package/dist/index.d.ts CHANGED
@@ -49,6 +49,14 @@ declare const StageSchema: z.ZodObject<{
49
49
  id: z.ZodString;
50
50
  title: z.ZodOptional<z.ZodString>;
51
51
  summary: z.ZodOptional<z.ZodString>;
52
+ /**
53
+ * standard — normal work stage
54
+ * brainstorm — optional multi-approach exploration (before design)
55
+ * clarify — optional Q&A before leaving a major stage
56
+ */
57
+ kind: z.ZodDefault<z.ZodEnum<["standard", "brainstorm", "clarify"]>>;
58
+ /** When true, stage is optional (skippable); agents may skip if not needed */
59
+ optional: z.ZodDefault<z.ZodBoolean>;
52
60
  artifacts: z.ZodDefault<z.ZodArray<z.ZodObject<{
53
61
  id: z.ZodString;
54
62
  path: z.ZodString;
@@ -131,6 +139,8 @@ declare const StageSchema: z.ZodObject<{
131
139
  }>>;
132
140
  }, "strip", z.ZodTypeAny, {
133
141
  id: string;
142
+ kind: "standard" | "brainstorm" | "clarify";
143
+ optional: boolean;
134
144
  artifacts: {
135
145
  path: string;
136
146
  id: string;
@@ -165,6 +175,8 @@ declare const StageSchema: z.ZodObject<{
165
175
  id: string;
166
176
  title?: string | undefined;
167
177
  summary?: string | undefined;
178
+ kind?: "standard" | "brainstorm" | "clarify" | undefined;
179
+ optional?: boolean | undefined;
168
180
  artifacts?: {
169
181
  path: string;
170
182
  id: string;
@@ -202,6 +214,14 @@ declare const WorkflowSchema: z.ZodObject<{
202
214
  id: z.ZodString;
203
215
  title: z.ZodOptional<z.ZodString>;
204
216
  summary: z.ZodOptional<z.ZodString>;
217
+ /**
218
+ * standard — normal work stage
219
+ * brainstorm — optional multi-approach exploration (before design)
220
+ * clarify — optional Q&A before leaving a major stage
221
+ */
222
+ kind: z.ZodDefault<z.ZodEnum<["standard", "brainstorm", "clarify"]>>;
223
+ /** When true, stage is optional (skippable); agents may skip if not needed */
224
+ optional: z.ZodDefault<z.ZodBoolean>;
205
225
  artifacts: z.ZodDefault<z.ZodArray<z.ZodObject<{
206
226
  id: z.ZodString;
207
227
  path: z.ZodString;
@@ -284,6 +304,8 @@ declare const WorkflowSchema: z.ZodObject<{
284
304
  }>>;
285
305
  }, "strip", z.ZodTypeAny, {
286
306
  id: string;
307
+ kind: "standard" | "brainstorm" | "clarify";
308
+ optional: boolean;
287
309
  artifacts: {
288
310
  path: string;
289
311
  id: string;
@@ -318,6 +340,8 @@ declare const WorkflowSchema: z.ZodObject<{
318
340
  id: string;
319
341
  title?: string | undefined;
320
342
  summary?: string | undefined;
343
+ kind?: "standard" | "brainstorm" | "clarify" | undefined;
344
+ optional?: boolean | undefined;
321
345
  artifacts?: {
322
346
  path: string;
323
347
  id: string;
@@ -397,6 +421,8 @@ declare const WorkflowSchema: z.ZodObject<{
397
421
  version: number;
398
422
  stages: {
399
423
  id: string;
424
+ kind: "standard" | "brainstorm" | "clarify";
425
+ optional: boolean;
400
426
  artifacts: {
401
427
  path: string;
402
428
  id: string;
@@ -448,6 +474,8 @@ declare const WorkflowSchema: z.ZodObject<{
448
474
  id: string;
449
475
  title?: string | undefined;
450
476
  summary?: string | undefined;
477
+ kind?: "standard" | "brainstorm" | "clarify" | undefined;
478
+ optional?: boolean | undefined;
451
479
  artifacts?: {
452
480
  path: string;
453
481
  id: string;
@@ -621,6 +649,14 @@ declare const ChangeMetaSchema: z.ZodObject<{
621
649
  id: z.ZodString;
622
650
  title: z.ZodOptional<z.ZodString>;
623
651
  summary: z.ZodOptional<z.ZodString>;
652
+ /**
653
+ * standard — normal work stage
654
+ * brainstorm — optional multi-approach exploration (before design)
655
+ * clarify — optional Q&A before leaving a major stage
656
+ */
657
+ kind: z.ZodDefault<z.ZodEnum<["standard", "brainstorm", "clarify"]>>;
658
+ /** When true, stage is optional (skippable); agents may skip if not needed */
659
+ optional: z.ZodDefault<z.ZodBoolean>;
624
660
  artifacts: z.ZodDefault<z.ZodArray<z.ZodObject<{
625
661
  id: z.ZodString;
626
662
  path: z.ZodString;
@@ -703,6 +739,8 @@ declare const ChangeMetaSchema: z.ZodObject<{
703
739
  }>>;
704
740
  }, "strip", z.ZodTypeAny, {
705
741
  id: string;
742
+ kind: "standard" | "brainstorm" | "clarify";
743
+ optional: boolean;
706
744
  artifacts: {
707
745
  path: string;
708
746
  id: string;
@@ -737,6 +775,8 @@ declare const ChangeMetaSchema: z.ZodObject<{
737
775
  id: string;
738
776
  title?: string | undefined;
739
777
  summary?: string | undefined;
778
+ kind?: "standard" | "brainstorm" | "clarify" | undefined;
779
+ optional?: boolean | undefined;
740
780
  artifacts?: {
741
781
  path: string;
742
782
  id: string;
@@ -771,6 +811,8 @@ declare const ChangeMetaSchema: z.ZodObject<{
771
811
  skip_stages: string[];
772
812
  extra_stages: {
773
813
  id: string;
814
+ kind: "standard" | "brainstorm" | "clarify";
815
+ optional: boolean;
774
816
  artifacts: {
775
817
  path: string;
776
818
  id: string;
@@ -809,6 +851,8 @@ declare const ChangeMetaSchema: z.ZodObject<{
809
851
  id: string;
810
852
  title?: string | undefined;
811
853
  summary?: string | undefined;
854
+ kind?: "standard" | "brainstorm" | "clarify" | undefined;
855
+ optional?: boolean | undefined;
812
856
  artifacts?: {
813
857
  path: string;
814
858
  id: string;
@@ -923,6 +967,8 @@ declare const ChangeMetaSchema: z.ZodObject<{
923
967
  skip_stages: string[];
924
968
  extra_stages: {
925
969
  id: string;
970
+ kind: "standard" | "brainstorm" | "clarify";
971
+ optional: boolean;
926
972
  artifacts: {
927
973
  path: string;
928
974
  id: string;
@@ -999,6 +1045,8 @@ declare const ChangeMetaSchema: z.ZodObject<{
999
1045
  id: string;
1000
1046
  title?: string | undefined;
1001
1047
  summary?: string | undefined;
1048
+ kind?: "standard" | "brainstorm" | "clarify" | undefined;
1049
+ optional?: boolean | undefined;
1002
1050
  artifacts?: {
1003
1051
  path: string;
1004
1052
  id: string;
@@ -1120,17 +1168,34 @@ declare function clearActiveIf(projectRoot: string, config: Config, changeId: st
1120
1168
  declare function setActiveChange(projectRoot: string, config: Config, changeId: string): Promise<void>;
1121
1169
  declare function resolveChangeId(projectRoot: string, config: Config, changeId?: string): Promise<string>;
1122
1170
 
1171
+ /**
1172
+ * Prior non-skipped stages with required work must be complete before leaving
1173
+ * the current stage (prevents jumping intent → tasks with empty design).
1174
+ */
1175
+ declare function incompletePriorStageErrors(ctx: ChangeContext): Promise<string[]>;
1123
1176
  /** Artifact + gate + verify checks before leaving the current stage. */
1124
1177
  declare function canLeaveStage(ctx: ChangeContext, config: Config): Promise<{
1125
1178
  ok: boolean;
1126
1179
  errors: string[];
1127
1180
  warnings: string[];
1128
1181
  }>;
1182
+ /**
1183
+ * If meta.stage points past incomplete required work, return the earliest
1184
+ * incomplete stage id to fall back to (e.g. design).
1185
+ */
1186
+ declare function fallbackIncompleteStageId(ctx: ChangeContext): Promise<string | null>;
1129
1187
  declare function approveGate(projectRoot: string, config: Config, changeId: string, stageId: string | undefined, note?: string, status?: "approved" | "waived" | "failed"): Promise<ChangeContext>;
1130
1188
 
1131
1189
  declare function formatStatus(ctx: ChangeContext): string;
1132
1190
  declare function buildAgentPrompt(ctx: ChangeContext, config: Config, projectRoot: string): Promise<string>;
1133
1191
 
1192
+ /**
1193
+ * Artifact content quality — existence alone is not "stage complete"
1194
+ * (templates are materialized on stage entry and would otherwise auto-pass).
1195
+ */
1196
+ /** Strip comments/headings/empty bullets; require real prose. */
1197
+ declare function isSubstantiveArtifactContent(content: string): boolean;
1198
+
1134
1199
  interface CreateChangeInput {
1135
1200
  projectRoot: string;
1136
1201
  config: Config;
@@ -1276,11 +1341,13 @@ declare function agentKickoffMessage(opts: {
1276
1341
  title: string;
1277
1342
  stage: string;
1278
1343
  changeId: string;
1344
+ /** What just happened, e.g. "new change", "advanced to implement" */
1345
+ event?: string;
1279
1346
  }): string;
1280
1347
  /** Write .sdd/active-context.md for the current change. */
1281
1348
  declare function refreshActiveAgentContext(projectRoot: string): Promise<string | null>;
1282
1349
  /** Single playbook — only long agent-facing content we author once. */
1283
- declare const PROTOCOL_MD = "# SDD protocol\n\nThis repo uses **Structured Vibe Coding** (`sdd`). Agents must follow this file plus `.sdd/active-context.md`. Do not invent a parallel process.\n\n## Source of truth\n\n| What | Where |\n|------|--------|\n| Process state | `changes/<id>/meta.yaml` (workflow, stage, gates) |\n| Current task snapshot | `.sdd/active-context.md` |\n| Stable product/tech rules | `memory/*.md` (start at `memory/index.md` if present) |\n| Stage artifacts | files in `changes/<id>/` for the active change |\n\n## Required read order\n\n1. `.sdd/active-context.md`\n2. This file (`.sdd/protocol.md`)\n3. `meta.yaml` + artifacts for the **current stage** (and prior stages if needed)\n4. `memory/index.md` (documentation map) if present, then linked memory pages when architecture or conventions matter\n\n## Stage behavior\n\n| Stage kind | Agent focus |\n|------------|-------------|\n| intent / feature / stories | Clarify scope; write short artifacts |\n| design / lld / db / research / hl_arb | Specs and decisions; minimal product code |\n| tasks | Break work into implementable checklist |\n| implement | Code only for this change; follow tasks/acceptance |\n| local_verify | Check acceptance; fix only; prepare for complete |\n\n## Hard rules\n\n1. **Do not skip hard gates.** If blocked, tell the human to run `sdd gate approve` (or fix the gate).\n2. **Stay in scope** of the active change title/intent. No drive-by refactors or new features.\n3. **Honor constraints** in `arb-decision.md`, design, and memory non-negotiables.\n4. **Local verify** is part of done when the workflow has a verify stage: `sdd verify`.\n5. Prefer small, reviewable diffs.\n\n## Commands (human / shell)\n\n```bash\nsdd status\nsdd next\nsdd agent # print handoff\nsdd agents refresh # refresh active-context.md\nsdd verify\nsdd complete\n```\n\n## Out of scope for agents\n\n- Replacing CI/CD or team process tools\n- Deleting `.sdd` / archive history\n- Claiming complete without verify when required\n";
1350
+ declare const PROTOCOL_MD = "# SDD protocol\n\nThis repo uses **Structured Vibe Coding** (`sdd`). Agents must follow this file plus `.sdd/active-context.md`. Do not invent a parallel process.\n\n## Source of truth\n\n| What | Where |\n|------|--------|\n| Process state | `changes/<id>/meta.yaml` (workflow, stage, gates) |\n| Current task snapshot | `.sdd/active-context.md` |\n| Non-negotiables | `memory/constitution.md` if present |\n| Stable product/tech rules | `memory/*.md` (start at `memory/index.md` if present) |\n| Stage artifacts | files in `changes/<id>/` for the active change |\n\n## Required read order\n\n1. `.sdd/active-context.md`\n2. This file (`.sdd/protocol.md`)\n3. `memory/constitution.md` if present (non-negotiables agents must not violate)\n4. `meta.yaml` + artifacts for the **current stage** (and prior stages if needed)\n5. `memory/index.md` (documentation map) if present, then linked memory pages when architecture or conventions matter\n\n## Stage behavior\n\n| Stage kind | Agent focus |\n|------------|-------------|\n| intent / feature / stories | Clarify scope; write short artifacts |\n| design / lld / db / research / hl_arb | Specs and decisions; minimal product code |\n| tasks | Break work into implementable checklist |\n| implement | Code only for this change; follow tasks/acceptance |\n| local_verify | Check acceptance; fix only; prepare for complete |\n| brainstorm (optional) | Multiple approaches; parallel exploration; no final design yet |\n| clarify_* (optional) | Ask clarifying questions; write Q&A; or tell human to skip |\n\n## Optional stages\n\n- Stages marked **optional** / kind `brainstorm` or `clarify` may be skipped: \n `sdd skip <stage_id> -r \"not needed\"`\n- Do **not** invent a skip by jumping stages. `sdd next` blocks on empty required stubs (e.g. design.md).\n- If the tool falls back to an earlier stage, **complete that stage** before advancing again.\n\n## Hard rules\n\n1. **Do not skip hard gates.** If blocked, tell the human to run `sdd gate approve` (or fix the gate).\n2. **Stay in scope** of the active change title/intent. No drive-by refactors or new features.\n3. **Honor constraints** in `arb-decision.md`, design, and memory non-negotiables.\n4. **Local verify** is part of done when the workflow has a verify stage: `sdd verify`.\n5. Prefer small, reviewable diffs.\n6. **Required stage artifacts must be substantive** \u2014 not empty templates. Design cannot be skipped via empty design.md.\n\n## Commands (human / shell)\n\n```bash\nsdd status\nsdd next\nsdd agent # print handoff\nsdd agents refresh # refresh active-context.md\nsdd verify\nsdd complete\n```\n\n## Out of scope for agents\n\n- Replacing CI/CD or team process tools\n- Deleting `.sdd` / completed change history\n- Claiming complete without verify when required\n";
1284
1351
 
1285
1352
  interface InitOptions {
1286
1353
  projectRoot: string;
@@ -1311,7 +1378,8 @@ interface VerifyResult {
1311
1378
  output: string;
1312
1379
  }[];
1313
1380
  checklist: string[];
1314
- evidencePath: string;
1381
+ /** Path to evidence dir when configured; null when no evidence_dir is set */
1382
+ evidencePath: string | null;
1315
1383
  /** False if any required command failed or was not run when required */
1316
1384
  ok: boolean;
1317
1385
  }
@@ -1346,6 +1414,13 @@ declare function writeYaml(path: string, data: unknown): Promise<void>;
1346
1414
  declare function listDirs(path: string): Promise<string[]>;
1347
1415
  declare function listFiles(path: string): Promise<string[]>;
1348
1416
  declare function copyDir(src: string, dest: string): Promise<void>;
1417
+ /**
1418
+ * Copy directory tree but never overwrite existing files (stable docs like
1419
+ * memory/constitution.md survive `sdd init --force`).
1420
+ */
1421
+ declare function copyDirSkipExisting(src: string, dest: string): Promise<void>;
1422
+ /** Write only when the path does not already exist. */
1423
+ declare function writeTextIfMissing(path: string, content: string): Promise<boolean>;
1349
1424
  declare function removePath(path: string): Promise<void>;
1350
1425
 
1351
- export { AGENT_INTEGRATIONS, AGENT_TARGET_OPTIONS, ALL_AGENT_TARGETS, type AdvanceResult, type AgentIntegration, type AgentTarget, type AgentTargetOption, type ArtifactDef, ArtifactDefSchema, type ChangeContext, type ChangeMeta, ChangeMetaSchema, type Complexity, type Config, ConfigSchema, type CreateChangeInput, DEFAULT_INIT_INTEGRATION, type Gate, GateSchema, type InitOptions, type InitResult, type InstallAgentOptions, type InstallAgentResult, type InstallAgentsOptions, type InstallAgentsResult, type InstalledAgentSnapshot, PROTOCOL_MD, SDD_AGENT_ROLES, SDD_DIR, type SddAgentRole, type SddAgentRoleId, type Stage, StageSchema, VerifyCommandSchema, type VerifyResult, type Workflow, WorkflowSchema, activePointerPath, activeStages, advanceStage, agentHostPaths, agentKickoffMessage, approveGate, archiveDir, buildAgentPrompt, buildContext, canLeaveStage, changeDirName, changeMetaPath, changePath, changesDir, clearActiveIf, completeChange, configPath, copyDir, createChange, defaultConfig, defaultMemoryDir, defaultTemplatesDir, defaultWorkflowsDir, defaultsRoot, domainsDir, ensureDir, firstActiveStageId, formatStatus, getActiveChangeId, getIntegration, getStage, inferComplexity, initProject, installAgentIntegration, installAgentIntegrations, integrationKeyFor, isInitialized, isStageSkipped, listChanges, listDirs, listFiles, listWorkflowNames, loadChangeMeta, loadConfig, loadInstalledAgent, loadWorkflow, memoryDir, nextStageId, nowIso, optionForTarget, parseAgentTargets, parseIntegration, pathExists, readText, readYaml, recommendWorkflow, refreshActiveAgentContext, removeOtherAgentHosts, removePath, renderThinAgent, resolveChangeId, resolveStages, runLocalVerify, saveChangeMeta, saveConfig, sddRoot, setActiveChange, shouldAutoSkip, skipStage, slugify, switchWorkflow, templatesDir, todayIsoDate, uniqueChangeDirName, workflowsDir, writeAgentHandoff, writeText, writeYaml };
1426
+ export { AGENT_INTEGRATIONS, AGENT_TARGET_OPTIONS, ALL_AGENT_TARGETS, type AdvanceResult, type AgentIntegration, type AgentTarget, type AgentTargetOption, type ArtifactDef, ArtifactDefSchema, type ChangeContext, type ChangeMeta, ChangeMetaSchema, type Complexity, type Config, ConfigSchema, type CreateChangeInput, DEFAULT_INIT_INTEGRATION, type Gate, GateSchema, type InitOptions, type InitResult, type InstallAgentOptions, type InstallAgentResult, type InstallAgentsOptions, type InstallAgentsResult, type InstalledAgentSnapshot, PROTOCOL_MD, SDD_AGENT_ROLES, SDD_DIR, type SddAgentRole, type SddAgentRoleId, type Stage, StageSchema, VerifyCommandSchema, type VerifyResult, type Workflow, WorkflowSchema, activePointerPath, activeStages, advanceStage, agentHostPaths, agentKickoffMessage, approveGate, archiveDir, buildAgentPrompt, buildContext, canLeaveStage, changeDirName, changeMetaPath, changePath, changesDir, clearActiveIf, completeChange, configPath, copyDir, copyDirSkipExisting, createChange, defaultConfig, defaultMemoryDir, defaultTemplatesDir, defaultWorkflowsDir, defaultsRoot, domainsDir, ensureDir, fallbackIncompleteStageId, firstActiveStageId, formatStatus, getActiveChangeId, getIntegration, getStage, incompletePriorStageErrors, inferComplexity, initProject, installAgentIntegration, installAgentIntegrations, integrationKeyFor, isInitialized, isStageSkipped, isSubstantiveArtifactContent, listChanges, listDirs, listFiles, listWorkflowNames, loadChangeMeta, loadConfig, loadInstalledAgent, loadWorkflow, memoryDir, nextStageId, nowIso, optionForTarget, parseAgentTargets, parseIntegration, pathExists, readText, readYaml, recommendWorkflow, refreshActiveAgentContext, removeOtherAgentHosts, removePath, renderThinAgent, resolveChangeId, resolveStages, runLocalVerify, saveChangeMeta, saveConfig, sddRoot, setActiveChange, shouldAutoSkip, skipStage, slugify, switchWorkflow, templatesDir, todayIsoDate, uniqueChangeDirName, workflowsDir, writeAgentHandoff, writeText, writeTextIfMissing, writeYaml };
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  clearActiveIf,
28
28
  configPath,
29
29
  copyDir,
30
+ copyDirSkipExisting,
30
31
  defaultConfig,
31
32
  domainsDir,
32
33
  ensureDir,
@@ -77,14 +78,84 @@ import {
77
78
  workflowsDir,
78
79
  writeAgentHandoff,
79
80
  writeText,
81
+ writeTextIfMissing,
80
82
  writeYaml
81
- } from "./chunk-4ZDPIYEB.js";
83
+ } from "./chunk-M245MC6E.js";
82
84
 
83
85
  // src/change.ts
84
86
  import { join as join2 } from "pathe";
85
87
 
86
88
  // src/stage-gates.ts
87
89
  import { join } from "pathe";
90
+
91
+ // src/artifacts.ts
92
+ function isSubstantiveArtifactContent(content) {
93
+ const stripped = content.replace(/<!--[\s\S]*?-->/g, "").replace(/^#{1,6}\s+.*$/gm, "").replace(/^>\s.*$/gm, "").replace(/^\s*[-*+]\s*$/gm, "").replace(/^\s*[-*+]\s*\.\.\.\s*$/gm, "").replace(/^\s*[-*+]\s*TBD\s*$/gim, "").replace(/^\s*[-*+]\s*TODO\s*$/gim, "").replace(/^\s*\|.*\|\s*$/gm, "").replace(/`+/g, "").trim();
94
+ if (stripped.length < 48) return false;
95
+ const meaningful = stripped.replace(/[-–—_.\s]/g, "").replace(/\b(TBD|TODO|N\/A|none|n\/a)\b/gi, "");
96
+ return meaningful.length >= 32;
97
+ }
98
+ function incompleteArtifactReason(path, content) {
99
+ if (content == null) {
100
+ return `Missing required artifact: ${path}`;
101
+ }
102
+ if (!isSubstantiveArtifactContent(content)) {
103
+ return `Required artifact still incomplete (looks like empty template/stub): ${path}. Fill it with real content, or skip this stage with \`sdd skip <stage> -r "\u2026"\` if optional.`;
104
+ }
105
+ return null;
106
+ }
107
+
108
+ // src/stage-gates.ts
109
+ async function checkRequiredArtifacts(changePath2, stageId, artifacts) {
110
+ const errors = [];
111
+ for (const artifact of artifacts) {
112
+ if (!artifact.required) continue;
113
+ if (artifact.path.endsWith("/")) {
114
+ const target2 = join(changePath2, artifact.path);
115
+ if (!await pathExists(target2)) {
116
+ errors.push(`Missing required directory: ${artifact.path} (stage ${stageId})`);
117
+ }
118
+ continue;
119
+ }
120
+ const target = join(changePath2, artifact.path);
121
+ if (!await pathExists(target)) {
122
+ errors.push(incompleteArtifactReason(artifact.path, null));
123
+ continue;
124
+ }
125
+ const content = await readText(target);
126
+ const reason = incompleteArtifactReason(artifact.path, content);
127
+ if (reason) {
128
+ errors.push(`${reason} (stage ${stageId})`);
129
+ }
130
+ }
131
+ return errors;
132
+ }
133
+ async function incompletePriorStageErrors(ctx) {
134
+ const errors = [];
135
+ const stages = activeStages(ctx.workflow, ctx.meta);
136
+ const currentIdx = stages.findIndex((s) => s.id === ctx.meta.stage);
137
+ if (currentIdx <= 0) return errors;
138
+ for (let i = 0; i < currentIdx; i++) {
139
+ const stage = stages[i];
140
+ if (isStageSkipped(ctx.meta, stage.id) || shouldAutoSkip(stage, ctx.meta)) {
141
+ continue;
142
+ }
143
+ const required = stage.artifacts.filter((a) => a.required);
144
+ if (!required.length && stage.optional) continue;
145
+ const stageErrors = await checkRequiredArtifacts(
146
+ ctx.path,
147
+ stage.id,
148
+ stage.artifacts
149
+ );
150
+ if (stageErrors.length) {
151
+ errors.push(
152
+ `Prior stage "${stage.id}" is not complete \u2014 finish it or \`sdd skip ${stage.id} -r "\u2026"\` if optional.`
153
+ );
154
+ errors.push(...stageErrors);
155
+ }
156
+ }
157
+ return errors;
158
+ }
88
159
  async function canLeaveStage(ctx, config) {
89
160
  const stage = getStage(ctx.workflow, ctx.meta, ctx.meta.stage);
90
161
  const errors = [];
@@ -92,13 +163,10 @@ async function canLeaveStage(ctx, config) {
92
163
  if (!stage) {
93
164
  return { ok: false, errors: [`Unknown stage: ${ctx.meta.stage}`], warnings };
94
165
  }
95
- for (const artifact of stage.artifacts) {
96
- if (!artifact.required) continue;
97
- const target = join(ctx.path, artifact.path);
98
- if (!await pathExists(target)) {
99
- errors.push(`Missing required artifact: ${artifact.path}`);
100
- }
101
- }
166
+ errors.push(...await incompletePriorStageErrors(ctx));
167
+ errors.push(
168
+ ...await checkRequiredArtifacts(ctx.path, stage.id, stage.artifacts)
169
+ );
102
170
  const declared = stage.gate?.type ?? "soft";
103
171
  let effectiveGate = declared;
104
172
  if (config.policy.gates === "hard" && declared === "soft") {
@@ -132,8 +200,27 @@ async function canLeaveStage(ctx, config) {
132
200
  );
133
201
  }
134
202
  }
203
+ if (stage.optional || stage.kind === "clarify" || stage.kind === "brainstorm") {
204
+ warnings.push(
205
+ `Stage "${stage.id}" is optional (${stage.kind ?? "optional"}). Skip with: sdd skip ${stage.id} -r "not needed"`
206
+ );
207
+ }
135
208
  return { ok: errors.length === 0, errors, warnings };
136
209
  }
210
+ async function fallbackIncompleteStageId(ctx) {
211
+ const stages = activeStages(ctx.workflow, ctx.meta);
212
+ for (const stage of stages) {
213
+ if (stage.id === ctx.meta.stage) break;
214
+ if (isStageSkipped(ctx.meta, stage.id) || shouldAutoSkip(stage, ctx.meta)) {
215
+ continue;
216
+ }
217
+ const required = stage.artifacts.filter((a) => a.required);
218
+ if (!required.length && stage.optional) continue;
219
+ const errs = await checkRequiredArtifacts(ctx.path, stage.id, stage.artifacts);
220
+ if (errs.length) return stage.id;
221
+ }
222
+ return null;
223
+ }
137
224
  async function approveGate(projectRoot, config, changeId, stageId, note, status = "approved") {
138
225
  const ctx = await buildContext(projectRoot, config, changeId);
139
226
  const id = stageId ?? ctx.meta.stage;
@@ -190,7 +277,7 @@ async function createChange(input) {
190
277
  await materializeStageArtifacts(projectRoot, config, id, workflow, meta, firstStage);
191
278
  await setActiveChange(projectRoot, config, id);
192
279
  try {
193
- const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-TN7TOYF7.js");
280
+ const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-BA7WVN35.js");
194
281
  await refreshActiveAgentContext2(projectRoot);
195
282
  } catch {
196
283
  }
@@ -252,11 +339,45 @@ function defaultArtifactContent(artifactId, meta, stage, workflow) {
252
339
  `;
253
340
  }
254
341
  async function advanceStage(projectRoot, config, changeId, opts) {
255
- const ctx = await buildContext(projectRoot, config, changeId);
342
+ let ctx = await buildContext(projectRoot, config, changeId);
256
343
  const warnings = [];
257
344
  if (ctx.meta.status === "completed") {
258
345
  throw new Error("Change is already completed.");
259
346
  }
347
+ if (!opts?.force) {
348
+ const fallback = await fallbackIncompleteStageId(ctx);
349
+ if (fallback && fallback !== ctx.meta.stage) {
350
+ const from2 = ctx.meta.stage;
351
+ ctx.meta.stage = fallback;
352
+ await saveChangeMeta(projectRoot, config, ctx.meta);
353
+ const stage2 = getStage(ctx.workflow, ctx.meta, fallback);
354
+ const artifactsCreated2 = await materializeStageArtifacts(
355
+ projectRoot,
356
+ config,
357
+ changeId,
358
+ ctx.workflow,
359
+ ctx.meta,
360
+ stage2
361
+ );
362
+ try {
363
+ const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-BA7WVN35.js");
364
+ await refreshActiveAgentContext2(projectRoot);
365
+ } catch {
366
+ }
367
+ const updated2 = await buildContext(projectRoot, config, changeId);
368
+ return {
369
+ ctx: updated2,
370
+ from: from2,
371
+ to: fallback,
372
+ completed: false,
373
+ artifactsCreated: artifactsCreated2,
374
+ warnings: [
375
+ ...warnings,
376
+ `Fell back to incomplete stage "${fallback}" (required work not done). Finish it before advancing.`
377
+ ]
378
+ };
379
+ }
380
+ }
260
381
  const check = await canLeaveStage(ctx, config);
261
382
  warnings.push(...check.warnings);
262
383
  if (!check.ok && !opts?.force) {
@@ -286,7 +407,7 @@ async function advanceStage(projectRoot, config, changeId, opts) {
286
407
  stage
287
408
  );
288
409
  try {
289
- const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-TN7TOYF7.js");
410
+ const { refreshActiveAgentContext: refreshActiveAgentContext2 } = await import("./agents-BA7WVN35.js");
290
411
  await refreshActiveAgentContext2(projectRoot);
291
412
  } catch {
292
413
  }
@@ -481,9 +602,9 @@ async function initProject(opts) {
481
602
  await ensureDir(mDest);
482
603
  const mSrc = defaultMemoryDir();
483
604
  if (await pathExists(mSrc)) {
484
- await copyDir(mSrc, mDest);
605
+ await copyDirSkipExisting(mSrc, mDest);
485
606
  } else {
486
- await writeText(
607
+ await writeTextIfMissing(
487
608
  join4(mDest, "index.md"),
488
609
  `# Documentation map
489
610
 
@@ -492,34 +613,93 @@ Parent page for **stable** project docs. Start here, then open only what you nee
492
613
  | Topic | File |
493
614
  |-------|------|
494
615
  | Product | [product.md](product.md) |
616
+ | Constitution (non-negotiables) | [constitution.md](constitution.md) |
495
617
  | Architecture | [architecture.md](architecture.md) |
496
618
  | Conventions | [conventions.md](conventions.md) |
497
619
 
498
620
  Active work: \`.sdd/active-context.md\` and \`changes/<id>/\`.
499
621
  `
500
622
  );
501
- await writeText(
623
+ await writeTextIfMissing(
502
624
  join4(mDest, "product.md"),
503
625
  "# Product\n\n<!-- What are you building? -->\n"
504
626
  );
505
- await writeText(
627
+ await writeTextIfMissing(
628
+ join4(mDest, "constitution.md"),
629
+ `# Constitution
630
+
631
+ <!-- Non-negotiables agents must not violate. Broader style lives in conventions.md. -->
632
+
633
+ ## Principles
634
+
635
+ -
636
+
637
+ ## Stack & tooling
638
+
639
+ -
640
+
641
+ ## Testing
642
+
643
+ -
644
+
645
+ ## Security
646
+
647
+ -
648
+
649
+ ## Process
650
+
651
+ -
652
+ `
653
+ );
654
+ await writeTextIfMissing(
506
655
  join4(mDest, "architecture.md"),
507
656
  "# Architecture\n\n<!-- High-level system shape -->\n"
508
657
  );
509
- await writeText(
658
+ await writeTextIfMissing(
510
659
  join4(mDest, "conventions.md"),
511
660
  "# Conventions\n\n<!-- Coding and process conventions -->\n"
512
661
  );
513
662
  }
663
+ if (!await pathExists(join4(mDest, "constitution.md"))) {
664
+ const fromDefault = join4(mSrc, "constitution.md");
665
+ if (await pathExists(fromDefault)) {
666
+ await copyDirSkipExisting(mSrc, mDest);
667
+ } else {
668
+ await writeTextIfMissing(
669
+ join4(mDest, "constitution.md"),
670
+ `# Constitution
671
+
672
+ <!-- Non-negotiables agents must not violate. Broader style lives in conventions.md. -->
673
+
674
+ ## Principles
675
+
676
+ -
677
+
678
+ ## Stack & tooling
679
+
680
+ -
681
+
682
+ ## Testing
683
+
684
+ -
685
+
686
+ ## Security
687
+
688
+ -
689
+
690
+ ## Process
691
+
692
+ -
693
+ `
694
+ );
695
+ }
696
+ }
514
697
  created.push(mDest);
515
698
  await ensureDir(changesDir(projectRoot, config));
516
- await ensureDir(archiveDir(projectRoot, config));
517
699
  await ensureDir(domainsDir(projectRoot, config));
518
700
  created.push(changesDir(projectRoot, config));
519
- created.push(archiveDir(projectRoot, config));
520
701
  created.push(domainsDir(projectRoot, config));
521
702
  await writeText(join4(changesDir(projectRoot, config), ".gitkeep"), "");
522
- await writeText(join4(archiveDir(projectRoot, config), ".gitkeep"), "");
523
703
  await writeText(join4(domainsDir(projectRoot, config), ".gitkeep"), "");
524
704
  const sddReadme = join4(root, "README.md");
525
705
  if (!await pathExists(sddReadme) || force) {
@@ -534,7 +714,8 @@ This directory configures local Spec-Driven Development for this repo.
534
714
  - \`templates/\` \u2014 markdown templates for stage artifacts
535
715
  - \`protocol.md\` / \`active-context.md\` \u2014 agent playbook + live task (after agent install)
536
716
 
537
- Work lives in \`../changes/\` and completed work in \`../archive/\`.
717
+ Work lives in \`../changes/<id>/\`. Completed packs stay there with \`status: completed\`.
718
+ Promote lasting decisions into \`../memory/\`. (Archive-on-complete is opt-in.)
538
719
 
539
720
  **Documentation map (stable):** \`../memory/index.md\`
540
721
  **Change-scoped docs:** \`../changes/<id>/\`
@@ -590,14 +771,59 @@ function runCommand(command, cwd) {
590
771
  });
591
772
  });
592
773
  }
774
+ function isToolGeneratedResults(content) {
775
+ const trimmed = content.trim();
776
+ if (!trimmed) return true;
777
+ if (trimmed.includes("{{") || trimmed.includes("TODO")) return true;
778
+ if (trimmed.startsWith("# Local verify results")) return true;
779
+ return false;
780
+ }
781
+ function buildSummaryLines(title, stageId, ok, results, checklist) {
782
+ const lines = [
783
+ `# Local verify results`,
784
+ ``,
785
+ `> Change: ${title}`,
786
+ `> Stage: ${stageId}`,
787
+ `> At: ${nowIso()}`,
788
+ `> Overall: ${ok ? "PASS" : "FAIL"}`,
789
+ ``,
790
+ `## Commands`,
791
+ ``
792
+ ];
793
+ if (results.length === 0) {
794
+ lines.push(`_No commands configured for this stage. Complete the checklist manually._`);
795
+ lines.push("");
796
+ } else {
797
+ for (const r of results) {
798
+ lines.push(`### ${r.name}`);
799
+ lines.push(`- Command: \`${r.command}\``);
800
+ lines.push(`- Exit: ${r.exitCode}`);
801
+ lines.push("");
802
+ }
803
+ }
804
+ lines.push(`## Checklist`);
805
+ lines.push("");
806
+ for (const item of checklist) {
807
+ lines.push(`- [ ] ${item}`);
808
+ }
809
+ lines.push("");
810
+ lines.push(`## Notes`);
811
+ lines.push("");
812
+ lines.push(`-`);
813
+ lines.push("");
814
+ return lines;
815
+ }
593
816
  async function runLocalVerify(projectRoot, config, changeId, opts) {
594
817
  const ctx = await buildContext(projectRoot, config, changeId);
595
818
  const stage = getStage(ctx.workflow, ctx.meta, ctx.meta.stage);
596
819
  if (!stage) {
597
820
  throw new Error(`Unknown stage: ${ctx.meta.stage}`);
598
821
  }
599
- const evidenceDir = stage.verify?.evidence_dir != null ? join5(ctx.path, stage.verify.evidence_dir) : join5(ctx.path, "evidence", "local");
600
- await ensureDir(evidenceDir);
822
+ const evidenceDirRaw = stage.verify?.evidence_dir?.trim();
823
+ const evidenceDir = evidenceDirRaw && evidenceDirRaw.length > 0 ? join5(ctx.path, evidenceDirRaw) : null;
824
+ if (evidenceDir) {
825
+ await ensureDir(evidenceDir);
826
+ }
601
827
  const results = [];
602
828
  const runCommands = opts?.runCommands !== false;
603
829
  const commands = stage.verify?.commands ?? [];
@@ -610,15 +836,17 @@ async function runLocalVerify(projectRoot, config, changeId, opts) {
610
836
  exitCode,
611
837
  output: output.slice(0, 5e4)
612
838
  });
613
- const logPath = join5(evidenceDir, `${cmd.name.replace(/\s+/g, "-")}.log`);
614
- await writeText(
615
- logPath,
616
- `# ${cmd.name}
839
+ if (evidenceDir) {
840
+ const logPath = join5(evidenceDir, `${cmd.name.replace(/\s+/g, "-")}.log`);
841
+ await writeText(
842
+ logPath,
843
+ `# ${cmd.name}
617
844
  $ ${cmd.run}
618
845
  exit: ${exitCode}
619
846
 
620
847
  ${output}`
621
- );
848
+ );
849
+ }
622
850
  }
623
851
  }
624
852
  const required = commands.filter((c) => c.required);
@@ -637,46 +865,44 @@ ${output}`
637
865
  }
638
866
  const checklist = stage.gate?.checklist ?? [];
639
867
  const summaryPath = join5(ctx.path, "local-test-results.md");
640
- const lines = [
641
- `# Local verify results`,
642
- ``,
643
- `> Change: ${ctx.meta.title}`,
644
- `> Stage: ${stage.id}`,
645
- `> At: ${nowIso()}`,
646
- `> Overall: ${ok ? "PASS" : "FAIL"}`,
647
- ``,
648
- `## Commands`,
649
- ``
650
- ];
651
- if (results.length === 0) {
652
- lines.push(`_No commands configured for this stage. Complete the checklist manually._`);
653
- lines.push("");
654
- } else {
655
- for (const r of results) {
656
- lines.push(`### ${r.name}`);
657
- lines.push(`- Command: \`${r.command}\``);
658
- lines.push(`- Exit: ${r.exitCode}`);
659
- lines.push("");
660
- }
661
- }
662
- lines.push(`## Checklist`);
663
- lines.push("");
664
- for (const item of checklist) {
665
- lines.push(`- [ ] ${item}`);
666
- }
667
- lines.push("");
668
- lines.push(`## Notes`);
669
- lines.push("");
670
- lines.push(`-`);
671
- lines.push("");
868
+ const lines = buildSummaryLines(ctx.meta.title, stage.id, ok, results, checklist);
869
+ const summaryBody = lines.join("\n");
672
870
  if (await pathExists(summaryPath)) {
673
- const stamp = nowIso();
674
- await writeText(
675
- join5(evidenceDir, `run-${stamp.replace(/[:.]/g, "-")}.md`),
676
- lines.join("\n")
677
- );
871
+ const existing = await readText(summaryPath);
872
+ if (isToolGeneratedResults(existing)) {
873
+ await writeText(summaryPath, summaryBody);
874
+ } else {
875
+ const stamp = nowIso();
876
+ const append = [
877
+ ``,
878
+ `---`,
879
+ ``,
880
+ `## Latest run (${stamp})`,
881
+ ``,
882
+ `> Overall: ${ok ? "PASS" : "FAIL"}`,
883
+ `> Stage: ${stage.id}`,
884
+ ``
885
+ ];
886
+ if (results.length === 0) {
887
+ append.push(`_No commands configured for this stage._`);
888
+ append.push("");
889
+ } else {
890
+ for (const r of results) {
891
+ append.push(`- **${r.name}**: exit ${r.exitCode} (\`${r.command}\`)`);
892
+ }
893
+ append.push("");
894
+ }
895
+ await writeText(summaryPath, existing.trimEnd() + "\n" + append.join("\n"));
896
+ }
897
+ if (evidenceDir) {
898
+ const stamp = nowIso();
899
+ await writeText(
900
+ join5(evidenceDir, `run-${stamp.replace(/[:.]/g, "-")}.md`),
901
+ summaryBody
902
+ );
903
+ }
678
904
  } else {
679
- await writeText(summaryPath, lines.join("\n"));
905
+ await writeText(summaryPath, summaryBody);
680
906
  }
681
907
  ctx.meta.verify_results = {
682
908
  ...ctx.meta.verify_results,
@@ -728,6 +954,7 @@ export {
728
954
  completeChange,
729
955
  configPath,
730
956
  copyDir,
957
+ copyDirSkipExisting,
731
958
  createChange,
732
959
  defaultConfig,
733
960
  defaultMemoryDir,
@@ -736,11 +963,13 @@ export {
736
963
  defaultsRoot,
737
964
  domainsDir,
738
965
  ensureDir,
966
+ fallbackIncompleteStageId,
739
967
  firstActiveStageId,
740
968
  formatStatus,
741
969
  getActiveChangeId,
742
970
  getIntegration,
743
971
  getStage,
972
+ incompletePriorStageErrors,
744
973
  inferComplexity,
745
974
  initProject,
746
975
  installAgentIntegration,
@@ -748,6 +977,7 @@ export {
748
977
  integrationKeyFor,
749
978
  isInitialized,
750
979
  isStageSkipped,
980
+ isSubstantiveArtifactContent,
751
981
  listChanges,
752
982
  listDirs,
753
983
  listFiles,
@@ -787,5 +1017,6 @@ export {
787
1017
  workflowsDir,
788
1018
  writeAgentHandoff,
789
1019
  writeText,
1020
+ writeTextIfMissing,
790
1021
  writeYaml
791
1022
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@structured-vibe-coding/core",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Core engine for structured vibe coding (local-first Spec-Driven Development)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",