@interf/compiler 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/README.md +126 -188
  2. package/builtin-workflows/interf/README.md +22 -10
  3. package/builtin-workflows/interf/compile/stages/shape/SKILL.md +6 -3
  4. package/builtin-workflows/interf/compile/stages/structure/SKILL.md +3 -0
  5. package/builtin-workflows/interf/compile/stages/summarize/SKILL.md +18 -2
  6. package/builtin-workflows/interf/improve/SKILL.md +2 -2
  7. package/builtin-workflows/interf/workflow.json +18 -4
  8. package/builtin-workflows/interf/{compiled.schema.json → workflow.schema.json} +9 -2
  9. package/dist/commands/check-draft.js +3 -3
  10. package/dist/commands/compile-controller.js +9 -16
  11. package/dist/commands/compile.d.ts +19 -1
  12. package/dist/commands/compile.js +98 -28
  13. package/dist/commands/create-workflow-wizard.d.ts +20 -2
  14. package/dist/commands/create-workflow-wizard.js +163 -27
  15. package/dist/commands/create.d.ts +1 -1
  16. package/dist/commands/create.js +67 -60
  17. package/dist/commands/dataset-selection.d.ts +6 -0
  18. package/dist/commands/dataset-selection.js +11 -0
  19. package/dist/commands/default.js +3 -3
  20. package/dist/commands/doctor.js +8 -8
  21. package/dist/commands/executor-flow.d.ts +1 -1
  22. package/dist/commands/executor-flow.js +5 -2
  23. package/dist/commands/init.d.ts +5 -0
  24. package/dist/commands/init.js +56 -48
  25. package/dist/commands/list.js +6 -3
  26. package/dist/commands/reset.js +1 -1
  27. package/dist/commands/source-config-wizard.d.ts +2 -2
  28. package/dist/commands/source-config-wizard.js +50 -17
  29. package/dist/commands/test-flow.js +5 -16
  30. package/dist/commands/test.d.ts +0 -6
  31. package/dist/commands/test.js +9 -17
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.js +1 -1
  34. package/dist/lib/agent-args.d.ts +1 -0
  35. package/dist/lib/agent-args.js +10 -0
  36. package/dist/lib/agent-execution.js +2 -1
  37. package/dist/lib/agent-preflight.js +2 -1
  38. package/dist/lib/agent-shells.d.ts +26 -1
  39. package/dist/lib/agent-shells.js +214 -40
  40. package/dist/lib/agents.d.ts +1 -1
  41. package/dist/lib/agents.js +1 -1
  42. package/dist/lib/builtin-compiled-workflow.d.ts +38 -0
  43. package/dist/lib/builtin-compiled-workflow.js +94 -0
  44. package/dist/lib/compiled-compile.d.ts +0 -4
  45. package/dist/lib/compiled-compile.js +11 -30
  46. package/dist/lib/compiled-paths.d.ts +1 -2
  47. package/dist/lib/compiled-paths.js +8 -13
  48. package/dist/lib/compiled-raw.d.ts +2 -2
  49. package/dist/lib/compiled-reset.d.ts +1 -0
  50. package/dist/lib/compiled-reset.js +42 -14
  51. package/dist/lib/compiled-schema.d.ts +11 -7
  52. package/dist/lib/compiled-schema.js +47 -16
  53. package/dist/lib/discovery.d.ts +1 -1
  54. package/dist/lib/discovery.js +2 -2
  55. package/dist/lib/executors.d.ts +1 -1
  56. package/dist/lib/executors.js +2 -2
  57. package/dist/lib/interf-detect.d.ts +0 -1
  58. package/dist/lib/interf-detect.js +7 -18
  59. package/dist/lib/interf-scaffold.js +4 -11
  60. package/dist/lib/interf-workflow-package.d.ts +8 -3
  61. package/dist/lib/interf-workflow-package.js +128 -62
  62. package/dist/lib/interf.d.ts +1 -1
  63. package/dist/lib/interf.js +1 -1
  64. package/dist/lib/local-workflows.d.ts +4 -3
  65. package/dist/lib/local-workflows.js +127 -104
  66. package/dist/lib/project-paths.d.ts +2 -4
  67. package/dist/lib/project-paths.js +13 -10
  68. package/dist/lib/runtime-acceptance.js +15 -3
  69. package/dist/lib/runtime-contracts.js +3 -2
  70. package/dist/lib/runtime-paths.d.ts +1 -0
  71. package/dist/lib/runtime-paths.js +4 -1
  72. package/dist/lib/runtime-prompt.js +4 -4
  73. package/dist/lib/runtime-reconcile.js +90 -64
  74. package/dist/lib/runtime-runs.js +29 -102
  75. package/dist/lib/runtime.d.ts +1 -1
  76. package/dist/lib/runtime.js +1 -1
  77. package/dist/lib/schema.d.ts +104 -54
  78. package/dist/lib/schema.js +32 -116
  79. package/dist/lib/source-config.js +21 -22
  80. package/dist/lib/state-health.js +4 -2
  81. package/dist/lib/state-io.js +2 -110
  82. package/dist/lib/state-view.js +8 -8
  83. package/dist/lib/state.d.ts +1 -0
  84. package/dist/lib/state.js +7 -0
  85. package/dist/lib/test-execution.js +2 -2
  86. package/dist/lib/test-paths.js +12 -3
  87. package/dist/lib/test-sandbox.js +4 -17
  88. package/dist/lib/test-specs.js +1 -1
  89. package/dist/lib/validate-compiled.js +13 -8
  90. package/dist/lib/validate.d.ts +5 -1
  91. package/dist/lib/validate.js +30 -22
  92. package/dist/lib/workflow-authoring.d.ts +26 -0
  93. package/dist/lib/workflow-authoring.js +119 -0
  94. package/dist/lib/workflow-definitions.d.ts +14 -3
  95. package/dist/lib/workflow-definitions.js +21 -17
  96. package/dist/lib/workflow-edit-session.d.ts +16 -0
  97. package/dist/lib/workflow-edit-session.js +57 -0
  98. package/dist/lib/workflow-edit-utils.d.ts +10 -0
  99. package/dist/lib/workflow-edit-utils.js +39 -0
  100. package/dist/lib/workflow-improvement.js +30 -217
  101. package/dist/lib/workflow-primitives.d.ts +2 -0
  102. package/dist/lib/workflow-primitives.js +5 -0
  103. package/dist/lib/workflow-stage-policy.d.ts +5 -0
  104. package/dist/lib/workflow-stage-policy.js +31 -0
  105. package/package.json +7 -8
  106. package/dist/lib/compiled-layout.d.ts +0 -2
  107. package/dist/lib/compiled-layout.js +0 -60
  108. package/dist/lib/obsidian.d.ts +0 -1
  109. package/dist/lib/obsidian.js +0 -15
  110. package/dist/lib/summarize-plan.d.ts +0 -17
  111. package/dist/lib/summarize-plan.js +0 -124
  112. package/dist/lib/workflow-abi.d.ts +0 -129
  113. package/dist/lib/workflow-abi.js +0 -156
@@ -6,9 +6,10 @@ import { isMarkdownFile } from "./util.js";
6
6
  import { listFilesRecursive } from "./filesystem.js";
7
7
  import { warnInterf } from "./logger.js";
8
8
  import { readJsonFileUnchecked, readJsonFileWithSchema } from "./parse.js";
9
- import { listBuiltinCompiledZoneSpecs, } from "./workflow-abi.js";
10
- import { WorkflowCompilerApiSchema, RuntimeContractTypeSchema, RuntimeStageAcceptanceSchema, WorkflowStageZoneAccessSchema, WorkflowCompiledSchemaSchema, WorkflowIdPattern, } from "./schema.js";
11
- import { writeCompiledSchemaFile, compiledSchemaFilePath } from "./compiled-schema.js";
9
+ import { listBuiltinCompiledZoneSpecs, } from "./builtin-compiled-workflow.js";
10
+ import { WorkflowCompilerApiSchema, RuntimeContractTypeSchema, RuntimeStageAcceptanceSchema, WorkflowPurposeSchema, WorkflowStageZoneAccessSchema, WorkflowIdPattern, } from "./schema.js";
11
+ import { WORKFLOW_SCHEMA_FILE, workflowSchemaExists, writeWorkflowSchemaDocument, workflowSchemaFilePath, readWorkflowSchemaFile, } from "./compiled-schema.js";
12
+ import { mergeStagePolicyNotesForStages } from "./workflow-stage-policy.js";
12
13
  const LocalWorkflowStageDefinitionSchema = z.object({
13
14
  id: z.string().regex(WorkflowIdPattern),
14
15
  label: z.string().min(1),
@@ -23,6 +24,7 @@ const LocalWorkflowDefinitionSchema = z.object({
23
24
  id: z.string().regex(WorkflowIdPattern),
24
25
  type: z.literal("compiled"),
25
26
  compiler_api: WorkflowCompilerApiSchema.optional(),
27
+ purpose: WorkflowPurposeSchema.optional(),
26
28
  label: z.string().min(1),
27
29
  hint: z.string().min(1),
28
30
  extends: z.string().regex(WorkflowIdPattern).optional(),
@@ -38,15 +40,49 @@ function builtinWorkflowRootPath(workflowId) {
38
40
  export function workflowDefinitionPath(sourcePath, id) {
39
41
  return join(workflowRootPath(sourcePath), id);
40
42
  }
43
+ function isSupportedWorkflowStarterDocPath(relativePath) {
44
+ if (relativePath === "README.md")
45
+ return true;
46
+ if (relativePath.startsWith("improve/"))
47
+ return true;
48
+ if (relativePath.startsWith("use/query/"))
49
+ return true;
50
+ if (relativePath.startsWith("compile/stages/"))
51
+ return true;
52
+ return false;
53
+ }
41
54
  function collectStarterDocs(dirPath) {
42
55
  return listFilesRecursive(dirPath, isMarkdownFile)
43
- .filter((filePath) => relative(dirPath, filePath).replaceAll("\\", "/") !== "workflow.json")
44
- .map((filePath) => ({
45
- relativePath: relative(dirPath, filePath).replaceAll("\\", "/"),
46
- content: readFileSync(filePath, "utf8"),
56
+ .map((filePath) => relative(dirPath, filePath).replaceAll("\\", "/"))
57
+ .filter((relativePath) => isSupportedWorkflowStarterDocPath(relativePath))
58
+ .map((relativePath) => ({
59
+ relativePath,
60
+ content: readFileSync(join(dirPath, relativePath), "utf8"),
47
61
  }))
48
62
  .sort((a, b) => a.relativePath.localeCompare(b.relativePath));
49
63
  }
64
+ function workflowPackageCopyPaths(dirPath) {
65
+ return [
66
+ "workflow.json",
67
+ WORKFLOW_SCHEMA_FILE,
68
+ ...collectStarterDocs(dirPath).map((doc) => doc.relativePath),
69
+ ];
70
+ }
71
+ function copyRelativeFile(sourceRoot, targetRoot, relativePath) {
72
+ const sourcePath = join(sourceRoot, relativePath);
73
+ if (!existsSync(sourcePath))
74
+ return;
75
+ const targetPath = join(targetRoot, relativePath);
76
+ mkdirSync(dirname(targetPath), { recursive: true });
77
+ cpSync(sourcePath, targetPath, { force: true });
78
+ }
79
+ function copyWorkflowPackageFiles(sourceWorkflowPath, targetWorkflowPath) {
80
+ rmSync(targetWorkflowPath, { recursive: true, force: true });
81
+ mkdirSync(targetWorkflowPath, { recursive: true });
82
+ for (const relativePath of workflowPackageCopyPaths(sourceWorkflowPath)) {
83
+ copyRelativeFile(sourceWorkflowPath, targetWorkflowPath, relativePath);
84
+ }
85
+ }
50
86
  export function loadWorkflowDefinitionFromDir(dirPath) {
51
87
  const workflowPath = existsSync(join(dirPath, "workflow.json"))
52
88
  ? join(dirPath, "workflow.json")
@@ -56,9 +92,8 @@ export function loadWorkflowDefinitionFromDir(dirPath) {
56
92
  const definition = readJsonFileWithSchema(workflowPath, "local workflow definition", LocalWorkflowDefinitionSchema);
57
93
  if (!definition)
58
94
  return null;
59
- const schemaPath = compiledSchemaFilePath(dirPath);
60
- const compiledSchema = readJsonFileWithSchema(schemaPath, "compiled schema", WorkflowCompiledSchemaSchema);
61
- if (!compiledSchema)
95
+ const workflowSchema = readWorkflowSchemaFile(dirPath);
96
+ if (!workflowSchema)
62
97
  return null;
63
98
  if (definition.type !== "compiled") {
64
99
  warnInterf(`Warning: local workflow definition at ${workflowPath} has unexpected type "${definition.type}".`);
@@ -66,11 +101,11 @@ export function loadWorkflowDefinitionFromDir(dirPath) {
66
101
  }
67
102
  return {
68
103
  ...definition,
69
- compiled_schema: compiledSchema,
104
+ workflow_schema: workflowSchema,
70
105
  starter_docs: collectStarterDocs(dirPath),
71
106
  directoryPath: dirPath,
72
107
  workflowPath,
73
- compiledSchemaPath: schemaPath,
108
+ workflowSchemaPath: workflowSchemaFilePath(dirPath),
74
109
  };
75
110
  }
76
111
  export function listLocalWorkflowDefinitions(sourcePath) {
@@ -108,36 +143,6 @@ export function resolveWorkflowPackageSourcePath(sourcePath, workflowId) {
108
143
  export function isWorkflowId(value) {
109
144
  return WorkflowIdPattern.test(value);
110
145
  }
111
- function normalizeStagePolicyNotes(value) {
112
- if (!value || typeof value !== "object" || Array.isArray(value))
113
- return undefined;
114
- const normalized = {};
115
- for (const [stageId, notes] of Object.entries(value)) {
116
- if (!Array.isArray(notes))
117
- continue;
118
- const cleaned = Array.from(new Set(notes
119
- .filter((note) => typeof note === "string")
120
- .map((note) => note.trim())
121
- .filter((note) => note.length > 0)));
122
- if (cleaned.length > 0) {
123
- normalized[stageId] = cleaned;
124
- }
125
- }
126
- return Object.keys(normalized).length > 0 ? normalized : undefined;
127
- }
128
- function mergeStagePolicyNotes(currentValue, nextValue) {
129
- const current = normalizeStagePolicyNotes(currentValue);
130
- if (!nextValue || Object.keys(nextValue).length === 0)
131
- return current;
132
- const merged = { ...(current ?? {}) };
133
- for (const [stageId, notes] of Object.entries(nextValue)) {
134
- merged[stageId] = Array.from(new Set([
135
- ...(merged[stageId] ?? []),
136
- ...notes.map((note) => note.trim()).filter((note) => note.length > 0),
137
- ]));
138
- }
139
- return Object.keys(merged).length > 0 ? merged : undefined;
140
- }
141
146
  function readWorkflowJsonObject(dirPath) {
142
147
  const workflowPath = join(dirPath, "workflow.json");
143
148
  const raw = readJsonFileUnchecked(workflowPath, "workflow package");
@@ -148,7 +153,6 @@ function readWorkflowJsonObject(dirPath) {
148
153
  }
149
154
  export function patchWorkflowPackageMetadata(dirPath, options = {}) {
150
155
  const workflowPath = join(dirPath, "workflow.json");
151
- const schemaPath = compiledSchemaFilePath(dirPath);
152
156
  const workflowJson = readWorkflowJsonObject(dirPath);
153
157
  const normalizedStages = Array.isArray(workflowJson.stages) && workflowJson.stages.length > 0
154
158
  ? workflowJson.stages
@@ -169,7 +173,7 @@ export function patchWorkflowPackageMetadata(dirPath, options = {}) {
169
173
  ...(options.hint ? { hint: options.hint } : {}),
170
174
  };
171
175
  delete nextWorkflowJson.extends;
172
- const mergedStagePolicyNotes = mergeStagePolicyNotes(workflowJson.stage_policy_notes, options.stagePolicyNotes);
176
+ const mergedStagePolicyNotes = mergeStagePolicyNotesForStages(normalizedStages, workflowJson.stage_policy_notes, options.stagePolicyNotes);
173
177
  if (mergedStagePolicyNotes) {
174
178
  nextWorkflowJson.stage_policy_notes = mergedStagePolicyNotes;
175
179
  }
@@ -177,33 +181,57 @@ export function patchWorkflowPackageMetadata(dirPath, options = {}) {
177
181
  delete nextWorkflowJson.stage_policy_notes;
178
182
  }
179
183
  writeFileSync(workflowPath, JSON.stringify(nextWorkflowJson, null, 2) + "\n");
180
- const schemaLabel = `${String(nextWorkflowJson.label ?? workflowJson.label ?? options.id ?? "Workflow")} compiled schema`;
181
- if (!existsSync(schemaPath)) {
182
- writeCompiledSchemaFile(dirPath, normalizedStages, schemaLabel);
183
- return;
184
+ const schemaLabel = `${String(nextWorkflowJson.label ?? workflowJson.label ?? options.id ?? "Workflow")} workflow schema`;
185
+ const existingSchema = readWorkflowSchemaFile(dirPath);
186
+ if (!existingSchema) {
187
+ throw new Error(`Cannot patch workflow package at ${dirPath}: missing ${WORKFLOW_SCHEMA_FILE}. Restore or reseed the package instead of regenerating a schema from workflow.json.`);
184
188
  }
185
- const rawSchema = readJsonFileUnchecked(schemaPath, "compiled schema");
186
- if (!rawSchema || typeof rawSchema !== "object" || Array.isArray(rawSchema))
187
- return;
188
- writeFileSync(schemaPath, JSON.stringify({
189
- ...rawSchema,
189
+ writeWorkflowSchemaDocument(dirPath, {
190
+ ...existingSchema,
190
191
  label: schemaLabel,
191
- }, null, 2) + "\n");
192
+ });
192
193
  }
193
- export function describeWorkflowPackagePortability(dirPath) {
194
+ function collectWorkflowPackageStructureIssues(dirPath, stages) {
194
195
  const issues = [];
195
196
  const workflowPath = join(dirPath, "workflow.json");
196
- const schemaPath = compiledSchemaFilePath(dirPath);
197
197
  if (!existsSync(workflowPath))
198
198
  issues.push("missing workflow.json");
199
- if (!existsSync(schemaPath))
200
- issues.push("missing compiled.schema.json");
199
+ if (!workflowSchemaExists(dirPath))
200
+ issues.push("missing workflow.schema.json");
201
201
  if (!existsSync(join(dirPath, "README.md")))
202
202
  issues.push("missing README.md");
203
203
  if (!existsSync(join(dirPath, "improve", "SKILL.md")))
204
204
  issues.push("missing improve/SKILL.md");
205
205
  if (!existsSync(join(dirPath, "use", "query", "SKILL.md")))
206
206
  issues.push("missing use/query/SKILL.md");
207
+ if (!stages)
208
+ return issues;
209
+ for (const stage of stages) {
210
+ const skillDir = typeof stage.skill_dir === "string" && stage.skill_dir.trim().length > 0
211
+ ? stage.skill_dir
212
+ : stage.id;
213
+ if (!existsSync(join(dirPath, "compile", "stages", skillDir, "SKILL.md"))) {
214
+ issues.push(`missing compile/stages/${skillDir}/SKILL.md`);
215
+ }
216
+ }
217
+ return issues;
218
+ }
219
+ function collectWorkflowStageSkillIssues(dirPath, stages) {
220
+ if (!stages)
221
+ return [];
222
+ const issues = [];
223
+ for (const stage of stages) {
224
+ const skillDir = typeof stage.skill_dir === "string" && stage.skill_dir.trim().length > 0
225
+ ? stage.skill_dir
226
+ : stage.id;
227
+ if (!existsSync(join(dirPath, "compile", "stages", skillDir, "SKILL.md"))) {
228
+ issues.push(`missing compile/stages/${skillDir}/SKILL.md`);
229
+ }
230
+ }
231
+ return issues;
232
+ }
233
+ export function describeWorkflowPackagePortability(dirPath) {
234
+ const issues = collectWorkflowPackageStructureIssues(dirPath, null);
207
235
  if (issues.length > 0)
208
236
  return issues;
209
237
  let workflowJson;
@@ -228,15 +256,10 @@ export function describeWorkflowPackagePortability(dirPath) {
228
256
  issues.push("workflow.json must declare explicit stages for a portable workflow package");
229
257
  return issues;
230
258
  }
231
- for (const stage of parsed.data.stages) {
232
- const skillDir = typeof stage.skill_dir === "string" && stage.skill_dir.trim().length > 0
233
- ? stage.skill_dir
234
- : stage.id;
235
- if (!existsSync(join(dirPath, "compile", "stages", skillDir, "SKILL.md"))) {
236
- issues.push(`missing compile/stages/${skillDir}/SKILL.md`);
237
- }
238
- }
239
- return issues;
259
+ return [
260
+ ...issues,
261
+ ...collectWorkflowStageSkillIssues(dirPath, parsed.data.stages ?? null),
262
+ ];
240
263
  }
241
264
  export function isPortableWorkflowPackage(dirPath) {
242
265
  return describeWorkflowPackagePortability(dirPath).length === 0;
@@ -246,13 +269,11 @@ export function copyWorkflowPackageDirectory(sourceWorkflowPath, targetWorkflowP
246
269
  if (portabilityIssues.length > 0) {
247
270
  throw new Error(`Workflow package at ${sourceWorkflowPath} is not directly copyable: ${portabilityIssues.join("; ")}.`);
248
271
  }
249
- rmSync(targetWorkflowPath, { recursive: true, force: true });
250
272
  mkdirSync(dirname(targetWorkflowPath), { recursive: true });
251
- cpSync(sourceWorkflowPath, targetWorkflowPath, { recursive: true });
273
+ copyWorkflowPackageFiles(sourceWorkflowPath, targetWorkflowPath);
252
274
  }
253
275
  export function validateWorkflowPackage(dirPath) {
254
276
  const workflowPath = join(dirPath, "workflow.json");
255
- const schemaPath = compiledSchemaFilePath(dirPath);
256
277
  if (!existsSync(workflowPath)) {
257
278
  return {
258
279
  ok: false,
@@ -288,38 +309,30 @@ export function validateWorkflowPackage(dirPath) {
288
309
  if (!def.stages || def.stages.length === 0) {
289
310
  errors.push("workflow.json must declare explicit stages. Legacy inherited packages are not portable.");
290
311
  }
291
- if (!existsSync(schemaPath)) {
292
- errors.push("Missing compiled.schema.json.");
293
- }
294
- if (!existsSync(join(dirPath, "README.md"))) {
295
- errors.push("Missing README.md.");
296
- }
297
- if (!existsSync(join(dirPath, "improve", "SKILL.md"))) {
298
- errors.push("Missing improve/SKILL.md.");
299
- }
300
- if (!existsSync(join(dirPath, "use", "query", "SKILL.md"))) {
301
- errors.push("Missing use/query/SKILL.md.");
302
- }
303
- const compiledSchema = readJsonFileWithSchema(schemaPath, "compiled schema", WorkflowCompiledSchemaSchema);
304
- if (!compiledSchema) {
305
- errors.push("compiled.schema.json is missing or invalid.");
306
- }
307
- if (def.stages && def.stages.length > 0) {
308
- for (const stage of def.stages) {
309
- const skillDir = typeof stage.skill_dir === "string" && stage.skill_dir.trim().length > 0
310
- ? stage.skill_dir
311
- : stage.id;
312
- if (!existsSync(join(dirPath, "compile", "stages", skillDir, "SKILL.md"))) {
313
- errors.push(`Missing compile/stages/${skillDir}/SKILL.md.`);
312
+ if (def.stage_policy_notes && def.stages) {
313
+ const stageIds = new Set(def.stages.map((stage) => stage.id));
314
+ for (const stageId of Object.keys(def.stage_policy_notes)) {
315
+ if (!stageIds.has(stageId)) {
316
+ errors.push(`workflow.json stage_policy_notes references unknown stage "${stageId}".`);
314
317
  }
315
318
  }
316
319
  }
317
- if (compiledSchema) {
320
+ for (const issue of collectWorkflowPackageStructureIssues(dirPath, def.stages ?? null)) {
321
+ const formatted = issue.startsWith("missing ")
322
+ ? `Missing ${issue.slice("missing ".length)}.`
323
+ : issue;
324
+ errors.push(formatted);
325
+ }
326
+ const workflowSchema = readWorkflowSchemaFile(dirPath);
327
+ if (!workflowSchema) {
328
+ errors.push("workflow.schema.json is missing or invalid.");
329
+ }
330
+ if (workflowSchema) {
318
331
  const stages = def.stages ?? [];
319
332
  const stageIds = new Set(stages.map((stage) => stage.id));
320
333
  const seenPaths = new Map();
321
334
  const seenZoneIds = new Set();
322
- const zoneById = new Map(compiledSchema.zones.map((zone) => [zone.id, zone]));
335
+ const zoneById = new Map(workflowSchema.zones.map((zone) => [zone.id, zone]));
323
336
  const normalizedParts = (value) => value.replaceAll("\\", "/").split("/").filter(Boolean);
324
337
  const hasOverlappingPath = (value, other) => {
325
338
  const a = normalizedParts(value);
@@ -328,26 +341,29 @@ export function validateWorkflowPackage(dirPath) {
328
341
  const longer = a.length <= b.length ? b : a;
329
342
  return shorter.every((part, index) => longer[index] === part);
330
343
  };
331
- for (const zone of compiledSchema.zones) {
344
+ for (const zone of workflowSchema.zones) {
332
345
  if (seenZoneIds.has(zone.id)) {
333
- errors.push(`compiled.schema.json repeats zone id "${zone.id}".`);
346
+ errors.push(`workflow.schema.json repeats zone id "${zone.id}".`);
334
347
  }
335
348
  seenZoneIds.add(zone.id);
336
349
  const existingPathOwner = seenPaths.get(zone.path);
337
350
  if (existingPathOwner) {
338
- errors.push(`compiled.schema.json repeats zone path "${zone.path}".`);
351
+ errors.push(`workflow.schema.json repeats zone path "${zone.path}".`);
339
352
  }
340
353
  for (const [existingPath, existingZoneId] of seenPaths.entries()) {
341
354
  if (existingPath === zone.path)
342
355
  continue;
343
356
  if (hasOverlappingPath(zone.path, existingPath)) {
344
- errors.push(`compiled.schema.json zones "${zone.id}" and "${existingZoneId}" overlap on path "${zone.path}" / "${existingPath}".`);
357
+ errors.push(`workflow.schema.json zones "${zone.id}" and "${existingZoneId}" overlap on path "${zone.path}" / "${existingPath}".`);
345
358
  }
346
359
  }
347
360
  seenPaths.set(zone.path, zone.id);
361
+ if ((zone.role === "input" || zone.role === "runtime") && zone.owned_by.length > 0) {
362
+ errors.push(`workflow.schema.json zone "${zone.id}" cannot declare owners because its role is "${zone.role}".`);
363
+ }
348
364
  for (const owner of zone.owned_by) {
349
365
  if (!stageIds.has(owner)) {
350
- errors.push(`compiled.schema.json references unknown stage "${owner}" for zone "${zone.path}".`);
366
+ errors.push(`workflow.schema.json references unknown stage "${owner}" for zone "${zone.path}".`);
351
367
  }
352
368
  }
353
369
  }
@@ -363,19 +379,25 @@ export function validateWorkflowPackage(dirPath) {
363
379
  errors.push(`Stage "${stage.id}" writes unknown zone "${zoneId}".`);
364
380
  continue;
365
381
  }
382
+ if (zone.role === "input" || zone.role === "runtime") {
383
+ errors.push(`Stage "${stage.id}" writes zone "${zoneId}" but zones with role "${zone.role}" are engine-owned inputs, not writable stage outputs.`);
384
+ }
366
385
  if (!zone.owned_by.includes(stage.id)) {
367
- errors.push(`Stage "${stage.id}" writes zone "${zoneId}" but that zone is not owned by the stage in compiled.schema.json.`);
386
+ errors.push(`Stage "${stage.id}" writes zone "${zoneId}" but that zone is not owned by the stage in workflow.schema.json.`);
368
387
  }
369
388
  }
370
389
  }
371
390
  for (const requiredZone of listBuiltinCompiledZoneSpecs().filter((zone) => zone.id === "raw" || zone.id === "runtime")) {
372
391
  const match = zoneById.get(requiredZone.id);
373
392
  if (!match) {
374
- errors.push(`compiled.schema.json is missing required zone "${requiredZone.id}".`);
393
+ errors.push(`workflow.schema.json is missing required zone "${requiredZone.id}".`);
375
394
  continue;
376
395
  }
377
396
  if (match.kind !== requiredZone.kind) {
378
- errors.push(`compiled.schema.json zone "${requiredZone.id}" should be kind "${requiredZone.kind}".`);
397
+ errors.push(`workflow.schema.json zone "${requiredZone.id}" should be kind "${requiredZone.kind}".`);
398
+ }
399
+ if (match.role !== requiredZone.role) {
400
+ errors.push(`workflow.schema.json zone "${requiredZone.id}" should have role "${requiredZone.role}".`);
379
401
  }
380
402
  }
381
403
  }
@@ -385,7 +407,8 @@ export function validateWorkflowPackage(dirPath) {
385
407
  const counts = {
386
408
  starter_docs: collectStarterDocs(dirPath).length,
387
409
  compile_stage_docs: stageDirs.length,
388
- compiled_zones: compiledSchema?.zones.length ?? 0,
410
+ compiled_zones: workflowSchema?.zones.length ?? 0,
411
+ workflow_zones: workflowSchema?.zones.length ?? 0,
389
412
  };
390
413
  return {
391
414
  ok: errors.length === 0,
@@ -1,13 +1,11 @@
1
1
  export declare const PROJECT_INTERF_DIR = "interf";
2
- export declare const PROJECT_WORKFLOW_DIR = "workflows";
3
- export declare const PROJECT_COMPILED_DIR = "compiled";
4
2
  export declare const PROJECT_TESTS_DIR = "tests";
5
3
  export declare function projectInterfRoot(projectPath: string): string;
6
- export declare function projectWorkflowRoot(projectPath: string): string;
7
- export declare function datasetArtifactRoot(projectPath: string, datasetName: string): string;
8
4
  export declare function compiledCompiledPathForDataset(projectPath: string, datasetName: string): string;
9
5
  export declare function datasetTestsRoot(projectPath: string, datasetName: string): string;
10
6
  export type DatasetTestTargetLabel = "file-as-is" | "compiled";
11
7
  export declare function datasetTestRunsRoot(projectPath: string, datasetName: string, target: DatasetTestTargetLabel): string;
12
8
  export declare function datasetLatestTestStatePath(projectPath: string, datasetName: string): string;
13
9
  export declare function datasetLatestTestSummaryPath(projectPath: string, datasetName: string): string;
10
+ export declare function normalizeDatasetTestRunId(input: string): string;
11
+ export declare function datasetTestRunPath(projectPath: string, datasetName: string, target: DatasetTestTargetLabel, generatedAt: string, runId: string, runSuffix?: string | null): string;
@@ -1,22 +1,14 @@
1
1
  import { join } from "node:path";
2
2
  export const PROJECT_INTERF_DIR = "interf";
3
- export const PROJECT_WORKFLOW_DIR = "workflows";
4
- export const PROJECT_COMPILED_DIR = "compiled";
5
3
  export const PROJECT_TESTS_DIR = "tests";
6
4
  export function projectInterfRoot(projectPath) {
7
5
  return join(projectPath, PROJECT_INTERF_DIR);
8
6
  }
9
- export function projectWorkflowRoot(projectPath) {
10
- return join(projectInterfRoot(projectPath), PROJECT_WORKFLOW_DIR);
11
- }
12
- export function datasetArtifactRoot(projectPath, datasetName) {
13
- return join(projectInterfRoot(projectPath), datasetName);
14
- }
15
7
  export function compiledCompiledPathForDataset(projectPath, datasetName) {
16
- return join(datasetArtifactRoot(projectPath, datasetName), PROJECT_COMPILED_DIR);
8
+ return join(projectInterfRoot(projectPath), datasetName);
17
9
  }
18
10
  export function datasetTestsRoot(projectPath, datasetName) {
19
- return join(datasetArtifactRoot(projectPath, datasetName), PROJECT_TESTS_DIR);
11
+ return join(projectInterfRoot(projectPath), PROJECT_TESTS_DIR, datasetName);
20
12
  }
21
13
  export function datasetTestRunsRoot(projectPath, datasetName, target) {
22
14
  return join(datasetTestsRoot(projectPath, datasetName), target, "runs");
@@ -27,3 +19,14 @@ export function datasetLatestTestStatePath(projectPath, datasetName) {
27
19
  export function datasetLatestTestSummaryPath(projectPath, datasetName) {
28
20
  return join(datasetTestsRoot(projectPath, datasetName), "latest.md");
29
21
  }
22
+ export function normalizeDatasetTestRunId(input) {
23
+ return input
24
+ .toLowerCase()
25
+ .trim()
26
+ .replace(/[^a-z0-9]+/g, "-")
27
+ .replace(/^-+|-+$/g, "")
28
+ .slice(0, 80);
29
+ }
30
+ export function datasetTestRunPath(projectPath, datasetName, target, generatedAt, runId, runSuffix) {
31
+ return join(datasetTestRunsRoot(projectPath, datasetName, target), `${generatedAt.replace(/[:.]/g, "-")}-${runId}${runSuffix ? `-${normalizeDatasetTestRunId(runSuffix)}` : ""}.json`);
32
+ }
@@ -2,7 +2,7 @@ import { existsSync, } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { listFilesRecursive } from "./filesystem.js";
4
4
  import { readJsonFileWithSchema } from "./parse.js";
5
- import { compiledZoneAbsolutePath, findCompiledSchemaZone, readCompiledSchemaFile, } from "./compiled-schema.js";
5
+ import { WORKFLOW_SCHEMA_FILE, compiledZoneAbsolutePath, findCompiledSchemaZone, readCompiledSchemaFile, } from "./compiled-schema.js";
6
6
  import { RuntimeStageContractSchema, } from "./schema.js";
7
7
  import { stageContractPath } from "./runtime-paths.js";
8
8
  import { workflowPackagePathForCompiled } from "./compiled-paths.js";
@@ -24,6 +24,7 @@ function hasAcceptanceRules(acceptance) {
24
24
  Object.keys(acceptance.zone_counts_at_least ?? {}).length > 0 ||
25
25
  Object.keys(acceptance.zone_counts_at_least_counts ?? {}).length > 0 ||
26
26
  (acceptance.markdown_frontmatter_valid_zones?.length ?? 0) > 0 ||
27
+ Object.keys(acceptance.frontmatter_required_keys_in_zones ?? {}).length > 0 ||
27
28
  (acceptance.markdown_abstract_valid_zones?.length ?? 0) > 0 ||
28
29
  (acceptance.wikilinks_valid_in_zones?.length ?? 0) > 0 ||
29
30
  Object.keys(acceptance.artifacts_must_not_contain ?? {}).length > 0;
@@ -106,8 +107,8 @@ export function validateResolvedStageAcceptance(dirPath, options) {
106
107
  if (!schema) {
107
108
  return {
108
109
  ok: false,
109
- summary: `Stage acceptance failed for ${options.stageId}: missing compiled schema.`,
110
- failures: ["Missing workflow/compiled.schema.json."],
110
+ summary: `Stage acceptance failed for ${options.stageId}: missing workflow schema.`,
111
+ failures: [`Missing workflow/${WORKFLOW_SCHEMA_FILE}.`],
111
112
  };
112
113
  }
113
114
  const failures = [];
@@ -190,6 +191,17 @@ export function validateResolvedStageAcceptance(dirPath, options) {
190
191
  failures.push(`Zone "${zoneId}" has ${validation.invalid_frontmatter} markdown file(s) with invalid or incomplete frontmatter.`);
191
192
  }
192
193
  }
194
+ for (const [zoneId, requiredKeys] of Object.entries(acceptance?.frontmatter_required_keys_in_zones ?? {})) {
195
+ const zone = findCompiledSchemaZone(schema, zoneId);
196
+ if (!zone) {
197
+ failures.push(`Acceptance references unknown zone "${zoneId}".`);
198
+ continue;
199
+ }
200
+ const validation = validateSynthFiles(markdownFilesForZone(dirPath, zone.path, zone.kind), { requiredFrontmatterKeys: requiredKeys });
201
+ if (validation.invalid_frontmatter > 0) {
202
+ failures.push(`Zone "${zoneId}" has ${validation.invalid_frontmatter} markdown file(s) missing required frontmatter keys (${requiredKeys.join(", ")}).`);
203
+ }
204
+ }
193
205
  for (const zoneId of acceptance?.markdown_abstract_valid_zones ?? []) {
194
206
  const zone = findCompiledSchemaZone(schema, zoneId);
195
207
  if (!zone) {
@@ -1,3 +1,4 @@
1
+ import { WORKFLOW_SCHEMA_FILE } from "./compiled-schema.js";
1
2
  export function buildRuntimeStageContract(options) {
2
3
  const localSkillDocs = options.localSkillDocs ?? [];
3
4
  const workflowNotes = options.workflowNotes ?? [];
@@ -11,7 +12,7 @@ export function buildRuntimeStageContract(options) {
11
12
  artifacts: {
12
13
  reads: [
13
14
  "interf.json",
14
- "workflow/compiled.schema.json",
15
+ `workflow/${WORKFLOW_SCHEMA_FILE}`,
15
16
  ...(options.extraReadArtifacts ?? []),
16
17
  ...options.stageReadArtifacts,
17
18
  ...localSkillDocs,
@@ -27,7 +28,7 @@ export function buildRuntimeStageContract(options) {
27
28
  notes: [
28
29
  `This is the "${options.stageLabel}" stage for the compiled dataset "${options.compiledName}".`,
29
30
  "The workflow package is the authoritative method layer for this run.",
30
- "Use `workflow/compiled.schema.json` as the deterministic output-shape reference for this compiled dataset.",
31
+ `Use \`workflow/${WORKFLOW_SCHEMA_FILE}\` as the deterministic zone-contract reference for this compiled dataset.`,
31
32
  "Honor the declared read and write artifacts instead of inventing a parallel workflow.",
32
33
  "Only create or update files that fall under the declared write targets for this stage.",
33
34
  "The CLI owns `.interf/runtime/` ledgers such as run, state, health, and view-spec files. Read them if needed, but do not create, edit, or replace them in this stage unless the contract explicitly marks a runtime artifact as stage-owned.",
@@ -1,6 +1,7 @@
1
1
  export declare function runPath(dirPath: string): string;
2
2
  export declare function runHistoryPath(dirPath: string): string;
3
3
  export declare function stageContractPath(dirPath: string): string;
4
+ export declare function archivedStageContractPath(dirPath: string, runId: string): string;
4
5
  export declare function logsDirPath(dirPath: string): string;
5
6
  export declare function promptLogPath(dirPath: string, runId: string): string;
6
7
  export declare function eventLogPath(dirPath: string, runId: string): string;
@@ -1,5 +1,5 @@
1
1
  import { join } from "node:path";
2
- import { compiledRuntimeLogsRoot, compiledRuntimeRunHistoryPath, compiledRuntimeRunPath, compiledRuntimeStageContractPath, } from "./compiled-paths.js";
2
+ import { compiledRuntimeArchivedStageContractPath, compiledRuntimeLogsRoot, compiledRuntimeRunHistoryPath, compiledRuntimeRunPath, compiledRuntimeStageContractPath, } from "./compiled-paths.js";
3
3
  export function runPath(dirPath) {
4
4
  return compiledRuntimeRunPath(dirPath);
5
5
  }
@@ -9,6 +9,9 @@ export function runHistoryPath(dirPath) {
9
9
  export function stageContractPath(dirPath) {
10
10
  return compiledRuntimeStageContractPath(dirPath);
11
11
  }
12
+ export function archivedStageContractPath(dirPath, runId) {
13
+ return compiledRuntimeArchivedStageContractPath(dirPath, runId);
14
+ }
12
15
  export function logsDirPath(dirPath) {
13
16
  return compiledRuntimeLogsRoot(dirPath);
14
17
  }
@@ -1,7 +1,5 @@
1
1
  export function buildStagePrompt(instructions, contractPath, statusLines) {
2
- const stageSkillDir = instructions.stage_skill_dir ||
3
- (instructions.bundled_skill?.split(/[\\/]/).filter(Boolean).pop()) ||
4
- "stage";
2
+ const stageSkillDir = instructions.stage_skill_dir || "stage";
5
3
  const modeNotes = instructions.effective_mode === "override"
6
4
  ? [
7
5
  "Local stage instruction docs are authoritative for this run.",
@@ -27,11 +25,13 @@ export function buildStagePrompt(instructions, contractPath, statusLines) {
27
25
  "For file-zone mounts, `runtime/paths.json` also gives the exact file path inside the `inputs/` or `outputs/` mount root.",
28
26
  "Honor the contract's counts, artifact paths, and policies instead of inventing another workflow.",
29
27
  "Read only the files named by the contract's `artifacts.reads` list plus any paths they explicitly direct you to open.",
28
+ "Prefer direct file-reading and search tools over shell commands when you inspect inputs, docs, or generated outputs.",
29
+ "Do not use shell helpers like `cat`, `sed`, `ls`, or `find` for routine file inspection if a native read/search tool can do the same job.",
30
30
  "If the contract lists `instructions.local_docs`, open every one of those files before you write artifacts or declare the stage complete.",
31
31
  "If `AGENTS.md` is listed in the contract, use it only for compiled routing that is directly relevant to this stage.",
32
32
  "`.interf/runtime/run.json`, `.interf/runtime/state.json`, `.interf/runtime/health.json`, and `.interf/runtime/view-spec.json` are CLI-owned runtime artifacts. You may read them, but do not create, edit, or replace them unless the contract explicitly marks a different proof artifact as stage-owned.",
33
33
  "If a contract-listed output file does not exist yet, create it in one whole-file write. Do not attempt patch-style edits against a missing runtime path.",
34
- "Stay inside the current compiled dataset. Do not try to open SDK repo docs, SDK source files, or other paths outside the compiled dataset unless the stage contract explicitly requires them.",
34
+ "Stay inside the current compiled dataset. Do not try to open repo docs, repo source files, or other paths outside the compiled dataset unless the stage contract explicitly requires them.",
35
35
  instructions.effective_mode === "override"
36
36
  ? "Do not depend on any repo-root or globally installed skill cache for this run. Local stage instruction docs are the primary workflow layer for this run."
37
37
  : "Do not depend on any repo-root or globally installed skill cache for this run. Use the current compiled workflow docs and the contract as the default behavior.",