@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
@@ -0,0 +1,119 @@
1
+ import { mkdtempSync, rmSync, } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { createWorkflowAuthoringShell } from "./agent-shells.js";
5
+ import { WORKFLOW_SCHEMA_FILE } from "./compiled-schema.js";
6
+ import { projectRawSnapshot } from "./compiled-raw.js";
7
+ import { seedLocalWorkflowPackageFromBase } from "./interf-workflow-package.js";
8
+ import { validateWorkflowPackage, workflowDefinitionPath } from "./local-workflows.js";
9
+ import { runWorkflowEditSession } from "./workflow-edit-session.js";
10
+ import { copyDirectory } from "./workflow-edit-utils.js";
11
+ import { createCompiled, compileCompiled } from "./workflows.js";
12
+ function buildWorkflowAuthoringPrompt() {
13
+ return [
14
+ "This is an automated Interf workflow-authoring run, not an open-ended chat session.",
15
+ "Execute it now.",
16
+ "Read `runtime/authoring-context.json` first.",
17
+ `Then read \`workflow/README.md\`, \`workflow/workflow.json\`, and \`workflow/${WORKFLOW_SCHEMA_FILE}\`.`,
18
+ "Review `artifacts/source-dataset/raw/` and any `artifacts/preview-compiled/` notes before editing the package.",
19
+ "Prefer direct file-reading and search tools over shell commands for routine file inspection.",
20
+ "Do not use shell helpers like `cat`, `sed`, `ls`, or `find` when a native read/search tool can inspect the same files.",
21
+ "Edit only files under `workflow/`.",
22
+ "Keep the workflow valid for the current compiler API and workflow schema.",
23
+ "Make the package materially more specific to this dataset task than the seeded base workflow. A no-op copy is not acceptable.",
24
+ "Prefer explicit stage-doc, stage-policy, purpose, and zone-contract edits over vague rewrites.",
25
+ "Do not introduce wikilinks unless the workflow also creates the target note by exact basename or explicit relative path.",
26
+ "Respect stage boundaries: a stage may only introduce links that resolve within that stage's declared writes or already-existing read zones.",
27
+ "Do not make one stage point at files or notes that are only created later by another stage.",
28
+ "Prefer conservative retrieval routing over speculative note sprawl.",
29
+ "Do not narrate plans or ask follow-up questions.",
30
+ "Only emit user-visible updates that begin with STATUS:, DONE:, BLOCKED:, or ERROR:.",
31
+ "As soon as the workflow edits are complete, stop.",
32
+ ].join("\n");
33
+ }
34
+ async function prepareWorkflowAuthoringPreview(options) {
35
+ const tempRoot = mkdtempSync(join(tmpdir(), "interf-workflow-preview-"));
36
+ projectRawSnapshot({
37
+ sourcePath: options.datasetPath,
38
+ destinationPath: tempRoot,
39
+ compiledPath: tempRoot,
40
+ mode: "copy",
41
+ prune: true,
42
+ preserveTimestamps: true,
43
+ });
44
+ copyDirectory(options.workflowPath, workflowDefinitionPath(tempRoot, options.workflowId));
45
+ const compiledPath = createCompiled("preview", tempRoot, options.workflowId, options.about, tempRoot);
46
+ const result = await compileCompiled({
47
+ executor: options.executor,
48
+ compiledPath,
49
+ ...(options.reporter ? { reporter: options.reporter } : {}),
50
+ preserveStageShells: "on-failure",
51
+ });
52
+ return {
53
+ tempRoot,
54
+ compiledPath,
55
+ compileResult: {
56
+ ok: result.ok,
57
+ failedStage: result.failedStage,
58
+ },
59
+ };
60
+ }
61
+ export async function runWorkflowAuthoringDraft(options) {
62
+ const workflowPath = seedLocalWorkflowPackageFromBase({
63
+ sourcePath: options.sourcePath,
64
+ baseWorkflowId: options.baseWorkflowId,
65
+ workflowId: options.workflowId,
66
+ label: options.label,
67
+ hint: options.hint,
68
+ });
69
+ let preview = null;
70
+ try {
71
+ if (options.preparePreview !== false) {
72
+ preview = await prepareWorkflowAuthoringPreview({
73
+ datasetPath: options.datasetPath,
74
+ about: options.taskPrompt,
75
+ workflowId: options.workflowId,
76
+ workflowPath,
77
+ executor: options.executor,
78
+ reporter: options.previewReporter ?? null,
79
+ });
80
+ }
81
+ const shell = createWorkflowAuthoringShell({
82
+ workflowPath,
83
+ workflowId: options.workflowId,
84
+ label: options.label,
85
+ baseWorkflowId: options.baseWorkflowId,
86
+ datasetPath: options.datasetPath,
87
+ taskPrompt: options.taskPrompt,
88
+ checks: options.checks ?? [],
89
+ preview,
90
+ });
91
+ if (preview) {
92
+ rmSync(preview.tempRoot, { recursive: true, force: true });
93
+ preview = null;
94
+ }
95
+ const session = await runWorkflowEditSession({
96
+ executor: options.executor,
97
+ workflowPath,
98
+ shell,
99
+ prompt: buildWorkflowAuthoringPrompt(),
100
+ validate: validateWorkflowPackage,
101
+ });
102
+ return {
103
+ status: session.status,
104
+ changed: session.changed,
105
+ summary: session.summary,
106
+ validation: session.validation,
107
+ workflowPath,
108
+ shellPath: shell.rootPath,
109
+ };
110
+ }
111
+ finally {
112
+ if (preview) {
113
+ rmSync(preview.tempRoot, { recursive: true, force: true });
114
+ }
115
+ }
116
+ }
117
+ export function resolveWorkflowDraftPath(sourcePath, workflowId) {
118
+ return workflowDefinitionPath(sourcePath, workflowId);
119
+ }
@@ -1,4 +1,4 @@
1
- import type { RuntimeContractType, RuntimeStageAcceptance, WorkflowCompilerApi, WorkflowId, WorkflowZoneId } from "./schema.js";
1
+ import type { RuntimeContractType, RuntimeStageAcceptance, WorkflowCompilerApi, WorkflowCompiledSchema, WorkflowId, WorkflowZoneId } from "./schema.js";
2
2
  export interface WorkflowStarterDoc {
3
3
  relativePath: string;
4
4
  content: string;
@@ -16,9 +16,14 @@ export interface WorkflowStageDefinition {
16
16
  export interface WorkflowDefinition<TId extends string> {
17
17
  id: TId;
18
18
  compilerApi?: WorkflowCompilerApi;
19
+ purpose?: {
20
+ label: string;
21
+ taskHint: string;
22
+ };
19
23
  label: string;
20
24
  hint: string;
21
25
  recommended?: boolean;
26
+ schema?: WorkflowCompiledSchema;
22
27
  stages: WorkflowStageDefinition[];
23
28
  stagePolicyNotes?: Record<string, string[]>;
24
29
  starterDocs?: WorkflowStarterDoc[];
@@ -28,6 +33,10 @@ export type CompiledWorkflowId = string;
28
33
  export declare function standaloneWorkflowDefinitionFromLocalPackage(local: {
29
34
  id: string;
30
35
  compiler_api?: WorkflowCompilerApi;
36
+ purpose?: {
37
+ label: string;
38
+ task_hint: string;
39
+ };
31
40
  label: string;
32
41
  hint: string;
33
42
  stages?: Array<{
@@ -41,6 +50,7 @@ export declare function standaloneWorkflowDefinitionFromLocalPackage(local: {
41
50
  acceptance?: RuntimeStageAcceptance;
42
51
  }>;
43
52
  stage_policy_notes?: Record<string, string[]>;
53
+ workflow_schema?: WorkflowCompiledSchema;
44
54
  starter_docs: WorkflowStarterDoc[];
45
55
  }): WorkflowDefinition<string>;
46
56
  export declare function listCompiledWorkflowChoices(sourcePath?: string): WorkflowDefinition<string>[];
@@ -48,8 +58,9 @@ export declare function getCompiledWorkflow(workflowId: CompiledWorkflowId, opti
48
58
  sourcePath?: string;
49
59
  }): WorkflowDefinition<string>;
50
60
  export declare function getActiveCompiledWorkflow(compiledPath: string, fallbackWorkflowId: CompiledWorkflowId): WorkflowDefinition<string>;
51
- export declare function resolveCompiledWorkflowId(value: unknown): CompiledWorkflowId;
52
- export declare function resolveCompiledWorkflowFromConfig(config: unknown): CompiledWorkflowId;
61
+ export declare function resolveCompiledWorkflowId(value: unknown): CompiledWorkflowId | null;
62
+ export declare function resolveCompiledWorkflowFromConfig(config: unknown): CompiledWorkflowId | null;
63
+ export declare function resolveRequiredCompiledWorkflowFromConfig(config: unknown, label?: string): CompiledWorkflowId;
53
64
  export declare function formatCompiledWorkflowStageStep(workflowId: CompiledWorkflowId, stage: string, options?: {
54
65
  sourcePath?: string;
55
66
  }): string;
@@ -5,6 +5,7 @@ import { PACKAGE_ROOT } from "./config.js";
5
5
  import { resolveSourceControlPath } from "./interf.js";
6
6
  import { warnInterf } from "./logger.js";
7
7
  import { workflowPackagePathForCompiled } from "./compiled-paths.js";
8
+ import { mergeStagePolicyNotesForStages } from "./workflow-stage-policy.js";
8
9
  let builtinInterfWorkflowCache = null;
9
10
  function toWorkflowStages(stages, fallbackStages) {
10
11
  if (!stages || stages.length === 0)
@@ -20,19 +21,6 @@ function toWorkflowStages(stages, fallbackStages) {
20
21
  ...(stage.acceptance ? { acceptance: stage.acceptance } : {}),
21
22
  }));
22
23
  }
23
- function mergeStagePolicyNotes(stages, baseNotes, overrideNotes) {
24
- const merged = {};
25
- for (const definition of stages) {
26
- const combined = Array.from(new Set([
27
- ...(baseNotes?.[definition.id] ?? []),
28
- ...((overrideNotes?.[definition.id] ?? []).filter((note) => note.trim().length > 0)),
29
- ]));
30
- if (combined.length > 0) {
31
- merged[definition.id] = combined;
32
- }
33
- }
34
- return Object.keys(merged).length > 0 ? merged : undefined;
35
- }
36
24
  export function standaloneWorkflowDefinitionFromLocalPackage(local) {
37
25
  if (!local.stages || local.stages.length === 0) {
38
26
  throw new Error(`Workflow package "${local.id}" is missing explicit stages. Compiled-local and portable workflow packages must be standalone.`);
@@ -44,11 +32,20 @@ export function standaloneWorkflowDefinitionFromLocalPackage(local) {
44
32
  kind: "compiled",
45
33
  version: 1,
46
34
  },
35
+ ...(local.purpose
36
+ ? {
37
+ purpose: {
38
+ label: local.purpose.label,
39
+ taskHint: local.purpose.task_hint,
40
+ },
41
+ }
42
+ : {}),
47
43
  label: local.label,
48
44
  hint: local.hint,
49
45
  recommended: false,
46
+ ...(local.workflow_schema ? { schema: local.workflow_schema } : {}),
50
47
  stages,
51
- stagePolicyNotes: mergeStagePolicyNotes(stages, undefined, local.stage_policy_notes),
48
+ stagePolicyNotes: mergeStagePolicyNotesForStages(stages, undefined, local.stage_policy_notes),
52
49
  starterDocs: [...local.starter_docs],
53
50
  scope: "local",
54
51
  };
@@ -113,7 +110,7 @@ export function getCompiledWorkflow(workflowId, options = {}) {
113
110
  return local;
114
111
  throw new Error(`No local workflow package found for "${workflowId}" under ${join(options.sourcePath, "interf", "workflows", workflowId)}.`);
115
112
  }
116
- throw new Error(`Workflow "${workflowId}" is not built-in. Provide a source path so Interf Compiler can resolve a local workflow package.`);
113
+ throw new Error(`Workflow "${workflowId}" is not built-in. Provide a source path so Interf can resolve a local workflow package.`);
117
114
  }
118
115
  export function getActiveCompiledWorkflow(compiledPath, fallbackWorkflowId) {
119
116
  const workflowPath = workflowPackagePathForCompiled(compiledPath);
@@ -133,14 +130,21 @@ export function resolveCompiledWorkflowId(value) {
133
130
  if (typeof value === "string" && isWorkflowId(value)) {
134
131
  return value;
135
132
  }
136
- return "interf";
133
+ return null;
137
134
  }
138
135
  export function resolveCompiledWorkflowFromConfig(config) {
139
136
  if (!config || typeof config !== "object")
140
- return "interf";
137
+ return null;
141
138
  const raw = config;
142
139
  return resolveCompiledWorkflowId(raw.workflow);
143
140
  }
141
+ export function resolveRequiredCompiledWorkflowFromConfig(config, label = "compiled config") {
142
+ const workflowId = resolveCompiledWorkflowFromConfig(config);
143
+ if (!workflowId) {
144
+ throw new Error(`Missing or invalid workflow in ${label}.`);
145
+ }
146
+ return workflowId;
147
+ }
144
148
  export function formatCompiledWorkflowStageStep(workflowId, stage, options = {}) {
145
149
  return formatWorkflowStageStep(getCompiledWorkflow(workflowId, options), stage);
146
150
  }
@@ -0,0 +1,16 @@
1
+ import type { WorkflowExecutor } from "./executors.js";
2
+ import type { WorkflowValidationResult } from "./local-workflows.js";
3
+ import { type WorkflowEditShellArtifacts } from "./workflow-edit-utils.js";
4
+ export interface WorkflowEditSessionResult {
5
+ status: "updated" | "no-change" | "invalid" | "executor-failed";
6
+ changed: boolean;
7
+ validation: WorkflowValidationResult | null;
8
+ summary: string;
9
+ }
10
+ export declare function runWorkflowEditSession(options: {
11
+ executor: WorkflowExecutor;
12
+ workflowPath: string;
13
+ shell: WorkflowEditShellArtifacts;
14
+ prompt: string;
15
+ validate: (workflowPath: string) => WorkflowValidationResult;
16
+ }): Promise<WorkflowEditSessionResult>;
@@ -0,0 +1,57 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { copyDirectory, directoriesMatch } from "./workflow-edit-utils.js";
3
+ export async function runWorkflowEditSession(options) {
4
+ copyDirectory(options.workflowPath, options.shell.workflowBeforePath);
5
+ writeFileSync(options.shell.promptLogPath, `${options.prompt}\n`);
6
+ let code = 1;
7
+ let executeError = null;
8
+ try {
9
+ code = await options.executor.execute(options.shell.rootPath, options.prompt, {
10
+ eventLogPath: options.shell.eventLogPath,
11
+ statusLogPath: options.shell.statusLogPath,
12
+ });
13
+ }
14
+ catch (error) {
15
+ executeError = error instanceof Error ? error.message : String(error);
16
+ }
17
+ copyDirectory(options.workflowPath, options.shell.workflowAfterPath);
18
+ const changed = !directoriesMatch(options.shell.workflowBeforePath, options.shell.workflowAfterPath);
19
+ if (executeError) {
20
+ copyDirectory(options.shell.workflowBeforePath, options.workflowPath);
21
+ return {
22
+ status: "executor-failed",
23
+ changed,
24
+ validation: null,
25
+ summary: `Workflow editor failed before completing: ${executeError}`,
26
+ };
27
+ }
28
+ if (code !== 0) {
29
+ copyDirectory(options.shell.workflowBeforePath, options.workflowPath);
30
+ return {
31
+ status: "executor-failed",
32
+ changed,
33
+ validation: null,
34
+ summary: `Workflow editor exited with code ${code}.`,
35
+ };
36
+ }
37
+ const validation = options.validate(options.workflowPath);
38
+ if (!validation.ok) {
39
+ copyDirectory(options.shell.workflowBeforePath, options.workflowPath);
40
+ return {
41
+ status: "invalid",
42
+ changed,
43
+ validation,
44
+ summary: changed
45
+ ? `Workflow package failed validation: ${validation.summary}`
46
+ : `Workflow package is invalid without any workflow edits: ${validation.summary}`,
47
+ };
48
+ }
49
+ return {
50
+ status: changed ? "updated" : "no-change",
51
+ changed,
52
+ validation,
53
+ summary: changed
54
+ ? "Workflow package updated and validated."
55
+ : "Workflow package is valid without additional edits.",
56
+ };
57
+ }
@@ -0,0 +1,10 @@
1
+ export declare function copyDirectory(sourcePath: string, targetPath: string): void;
2
+ export declare function directoriesMatch(leftPath: string, rightPath: string): boolean;
3
+ export interface WorkflowEditShellArtifacts {
4
+ rootPath: string;
5
+ workflowBeforePath: string;
6
+ workflowAfterPath: string;
7
+ promptLogPath: string;
8
+ eventLogPath: string;
9
+ statusLogPath: string;
10
+ }
@@ -0,0 +1,39 @@
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ export function copyDirectory(sourcePath, targetPath) {
4
+ rmSync(targetPath, { recursive: true, force: true });
5
+ mkdirSync(targetPath, { recursive: true });
6
+ cpSync(sourcePath, targetPath, { recursive: true });
7
+ }
8
+ function listFileSnapshot(dirPath) {
9
+ const snapshot = new Map();
10
+ if (!existsSync(dirPath))
11
+ return snapshot;
12
+ const collect = (currentPath) => {
13
+ for (const entry of readdirSync(currentPath)) {
14
+ const fullPath = join(currentPath, entry);
15
+ const stat = statSync(fullPath);
16
+ if (stat.isDirectory()) {
17
+ collect(fullPath);
18
+ continue;
19
+ }
20
+ if (!stat.isFile())
21
+ continue;
22
+ snapshot.set(relative(dirPath, fullPath), readFileSync(fullPath, "utf8"));
23
+ }
24
+ };
25
+ collect(dirPath);
26
+ return snapshot;
27
+ }
28
+ export function directoriesMatch(leftPath, rightPath) {
29
+ const left = listFileSnapshot(leftPath);
30
+ const right = listFileSnapshot(rightPath);
31
+ if (left.size !== right.size)
32
+ return false;
33
+ for (const [relativePath, content] of left.entries()) {
34
+ if (right.get(relativePath) !== content) {
35
+ return false;
36
+ }
37
+ }
38
+ return true;
39
+ }
@@ -1,4 +1,4 @@
1
- import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
1
+ import { appendFileSync, existsSync, mkdirSync, writeFileSync, } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
3
  import { createWorkflowImprovementShell, freezeWorkflowImprovementShell, } from "./agent-shells.js";
4
4
  import { readInterfConfig } from "./interf.js";
@@ -7,44 +7,9 @@ import { readJsonFileWithSchema } from "./parse.js";
7
7
  import { saveCompiledInterfConfig } from "./source-config.js";
8
8
  import { WorkflowImprovementRunLedgerSchema, } from "./schema.js";
9
9
  import { resolveWorkflowImprovementReviewPaths } from "./workflow-review-paths.js";
10
+ import { runWorkflowEditSession } from "./workflow-edit-session.js";
10
11
  import { targetTestRunsRootForCompiled, targetTestSandboxesRootForCompiled, workflowImprovementRunRoot, workflowPackagePathForCompiled, } from "./compiled-paths.js";
11
- function copyDirectory(sourcePath, targetPath) {
12
- rmSync(targetPath, { recursive: true, force: true });
13
- mkdirSync(targetPath, { recursive: true });
14
- cpSync(sourcePath, targetPath, { recursive: true });
15
- }
16
- function listFileSnapshot(dirPath) {
17
- const snapshot = new Map();
18
- if (!existsSync(dirPath))
19
- return snapshot;
20
- const collect = (currentPath) => {
21
- for (const entry of readdirSync(currentPath)) {
22
- const fullPath = join(currentPath, entry);
23
- const stat = statSync(fullPath);
24
- if (stat.isDirectory()) {
25
- collect(fullPath);
26
- continue;
27
- }
28
- if (!stat.isFile())
29
- continue;
30
- snapshot.set(relative(dirPath, fullPath), readFileSync(fullPath, "utf8"));
31
- }
32
- };
33
- collect(dirPath);
34
- return snapshot;
35
- }
36
- function directoriesMatch(leftPath, rightPath) {
37
- const left = listFileSnapshot(leftPath);
38
- const right = listFileSnapshot(rightPath);
39
- if (left.size !== right.size)
40
- return false;
41
- for (const [relativePath, content] of left.entries()) {
42
- if (right.get(relativePath) !== content) {
43
- return false;
44
- }
45
- }
46
- return true;
47
- }
12
+ import { WORKFLOW_SCHEMA_FILE } from "./compiled-schema.js";
48
13
  function toShellArtifactPath(absolutePath, sourceRoot, shellRoot) {
49
14
  if (!absolutePath)
50
15
  return null;
@@ -90,16 +55,18 @@ function buildLoopContext(options) {
90
55
  }
91
56
  function buildWorkflowImprovementPrompt() {
92
57
  return [
93
- "This is an automated Interf Compiler workflow-improvement run, not an open-ended chat session.",
58
+ "This is an automated Interf workflow-improvement run, not an open-ended chat session.",
94
59
  "The user already invoked this through `interf compile` with self-improving loops enabled. Execute it now.",
95
60
  "Read `runtime/loop-context.json` first.",
96
- "Then read `workflow/README.md`, `workflow/workflow.json`, `workflow/compiled.schema.json`, and `workflow/improve/SKILL.md` if it exists.",
61
+ `Then read \`workflow/README.md\`, \`workflow/workflow.json\`, \`workflow/${WORKFLOW_SCHEMA_FILE}\`, and \`workflow/improve/SKILL.md\` if it exists.`,
97
62
  "Review preserved evidence from earlier failures under `artifacts/` before you edit the workflow.",
98
63
  "Treat `workflow/improve/SKILL.md` as guidance for how to improve the workflow, not as the default file to edit.",
99
64
  "Prefer editing the stage docs, workflow contract, or schema ownership that actually change compiled outputs.",
100
65
  "Edit only files under `workflow/`.",
101
66
  "Do not edit truth checks, test specs, raw dataset files, or generated compiled outputs.",
102
- "Keep the workflow valid for the current compiler API and compiled schema.",
67
+ "Keep the workflow valid for the current compiler API and workflow schema.",
68
+ "Respect stage boundaries: a stage may only introduce links that resolve within that stage's declared writes or already-existing read zones.",
69
+ "Do not make one stage point at files or notes that are only created later by another stage.",
103
70
  "Prefer small, defensible changes to workflow docs, stage docs, stage policies, or schema ownership over random churn.",
104
71
  "Do not narrate plans or ask follow-up questions.",
105
72
  "Only emit user-visible updates that begin with STATUS:, DONE:, BLOCKED:, or ERROR:.",
@@ -193,170 +160,14 @@ export async function runWorkflowImprovementLoop(options) {
193
160
  context,
194
161
  });
195
162
  const workflowRoot = workflowPackagePathForCompiled(options.compiledPath);
196
- copyDirectory(workflowRoot, shell.workflowBeforePath);
197
- const prompt = buildWorkflowImprovementPrompt();
198
- writeFileSync(shell.promptLogPath, `${prompt}\n`);
199
- let code = 1;
200
- let executeError = null;
201
- try {
202
- code = await options.executor.execute(shell.rootPath, prompt, {
203
- eventLogPath: shell.eventLogPath,
204
- statusLogPath: shell.statusLogPath,
205
- });
206
- }
207
- catch (error) {
208
- executeError = error instanceof Error ? error.message : String(error);
209
- }
210
- copyDirectory(workflowRoot, shell.workflowAfterPath);
163
+ const session = await runWorkflowEditSession({
164
+ executor: options.executor,
165
+ workflowPath: workflowRoot,
166
+ shell,
167
+ prompt: buildWorkflowImprovementPrompt(),
168
+ validate: validateWorkflowPackage,
169
+ });
211
170
  const preservedShellManifestPath = freezeWorkflowImprovementShell(shell.rootPath);
212
- const changed = !directoriesMatch(shell.workflowBeforePath, shell.workflowAfterPath);
213
- if (executeError) {
214
- copyDirectory(shell.workflowBeforePath, workflowRoot);
215
- const summary = `Workflow improver failed before completing: ${executeError}`;
216
- writeWorkflowImprovementRunLedger(options.compiledPath, options.runId, buildWorkflowImprovementLoopRecord({
217
- targetName: compiledName,
218
- workflowId: options.workflowId,
219
- runId: options.runId,
220
- loopIndex: options.loopIndex,
221
- shellPath: shell.rootPath,
222
- loopRootPath: shell.loopRootPath,
223
- workflowBeforePath: shell.workflowBeforePath,
224
- workflowAfterPath: shell.workflowAfterPath,
225
- promptLogPath: shell.promptLogPath,
226
- eventLogPath: shell.eventLogPath,
227
- statusLogPath: shell.statusLogPath,
228
- preservedShellManifestPath,
229
- changed,
230
- validation: null,
231
- result: "executor-failed",
232
- summary,
233
- }), {
234
- targetName: compiledName,
235
- workflowId: options.workflowId,
236
- maxLoops: options.maxLoops,
237
- maxAttempts: options.maxAttempts,
238
- });
239
- return {
240
- status: "executor-failed",
241
- changed,
242
- validation: null,
243
- summary,
244
- shellPath: shell.rootPath,
245
- loopRootPath: shell.loopRootPath,
246
- };
247
- }
248
- if (code !== 0) {
249
- copyDirectory(shell.workflowBeforePath, workflowRoot);
250
- const summary = `Workflow improver exited with code ${code}.`;
251
- writeWorkflowImprovementRunLedger(options.compiledPath, options.runId, buildWorkflowImprovementLoopRecord({
252
- targetName: compiledName,
253
- workflowId: options.workflowId,
254
- runId: options.runId,
255
- loopIndex: options.loopIndex,
256
- shellPath: shell.rootPath,
257
- loopRootPath: shell.loopRootPath,
258
- workflowBeforePath: shell.workflowBeforePath,
259
- workflowAfterPath: shell.workflowAfterPath,
260
- promptLogPath: shell.promptLogPath,
261
- eventLogPath: shell.eventLogPath,
262
- statusLogPath: shell.statusLogPath,
263
- preservedShellManifestPath,
264
- changed,
265
- validation: null,
266
- result: "executor-failed",
267
- summary,
268
- }), {
269
- targetName: compiledName,
270
- workflowId: options.workflowId,
271
- maxLoops: options.maxLoops,
272
- maxAttempts: options.maxAttempts,
273
- });
274
- return {
275
- status: "executor-failed",
276
- changed,
277
- validation: null,
278
- summary,
279
- shellPath: shell.rootPath,
280
- loopRootPath: shell.loopRootPath,
281
- };
282
- }
283
- if (!changed) {
284
- const validation = validateWorkflowPackage(workflowRoot);
285
- const result = validation.ok ? "no-change" : "invalid";
286
- if (!validation.ok) {
287
- copyDirectory(shell.workflowBeforePath, workflowRoot);
288
- }
289
- const summary = validation.ok
290
- ? "Workflow improver made no workflow edits."
291
- : `Workflow variation is invalid without any workflow edits: ${validation.summary}`;
292
- writeWorkflowImprovementRunLedger(options.compiledPath, options.runId, buildWorkflowImprovementLoopRecord({
293
- targetName: compiledName,
294
- workflowId: options.workflowId,
295
- runId: options.runId,
296
- loopIndex: options.loopIndex,
297
- shellPath: shell.rootPath,
298
- loopRootPath: shell.loopRootPath,
299
- workflowBeforePath: shell.workflowBeforePath,
300
- workflowAfterPath: shell.workflowAfterPath,
301
- promptLogPath: shell.promptLogPath,
302
- eventLogPath: shell.eventLogPath,
303
- statusLogPath: shell.statusLogPath,
304
- preservedShellManifestPath,
305
- changed: false,
306
- validation,
307
- result,
308
- summary,
309
- }), {
310
- targetName: compiledName,
311
- workflowId: options.workflowId,
312
- maxLoops: options.maxLoops,
313
- maxAttempts: options.maxAttempts,
314
- });
315
- return {
316
- status: result,
317
- changed: false,
318
- validation,
319
- summary,
320
- shellPath: shell.rootPath,
321
- loopRootPath: shell.loopRootPath,
322
- };
323
- }
324
- const validation = validateWorkflowPackage(workflowRoot);
325
- if (!validation.ok) {
326
- copyDirectory(shell.workflowBeforePath, workflowRoot);
327
- const summary = `Workflow variation failed validation: ${validation.summary}`;
328
- writeWorkflowImprovementRunLedger(options.compiledPath, options.runId, buildWorkflowImprovementLoopRecord({
329
- targetName: compiledName,
330
- workflowId: options.workflowId,
331
- runId: options.runId,
332
- loopIndex: options.loopIndex,
333
- shellPath: shell.rootPath,
334
- loopRootPath: shell.loopRootPath,
335
- workflowBeforePath: shell.workflowBeforePath,
336
- workflowAfterPath: shell.workflowAfterPath,
337
- promptLogPath: shell.promptLogPath,
338
- eventLogPath: shell.eventLogPath,
339
- statusLogPath: shell.statusLogPath,
340
- preservedShellManifestPath,
341
- changed: true,
342
- validation,
343
- result: "invalid",
344
- summary,
345
- }), {
346
- targetName: compiledName,
347
- workflowId: options.workflowId,
348
- maxLoops: options.maxLoops,
349
- maxAttempts: options.maxAttempts,
350
- });
351
- return {
352
- status: "invalid",
353
- changed: true,
354
- validation,
355
- summary,
356
- shellPath: shell.rootPath,
357
- loopRootPath: shell.loopRootPath,
358
- };
359
- }
360
171
  writeWorkflowImprovementRunLedger(options.compiledPath, options.runId, buildWorkflowImprovementLoopRecord({
361
172
  targetName: compiledName,
362
173
  workflowId: options.workflowId,
@@ -370,26 +181,28 @@ export async function runWorkflowImprovementLoop(options) {
370
181
  eventLogPath: shell.eventLogPath,
371
182
  statusLogPath: shell.statusLogPath,
372
183
  preservedShellManifestPath,
373
- changed: true,
374
- validation,
375
- result: "updated",
376
- summary: "Workflow variation updated and validated.",
184
+ changed: session.changed,
185
+ validation: session.validation,
186
+ result: session.status,
187
+ summary: session.summary,
377
188
  }), {
378
189
  targetName: compiledName,
379
190
  workflowId: options.workflowId,
380
191
  maxLoops: options.maxLoops,
381
192
  maxAttempts: options.maxAttempts,
382
193
  });
383
- updateCompiledWorkflowOrigin({
384
- compiledPath: options.compiledPath,
385
- selectedWorkflowId: options.workflowId,
386
- localDraft: true,
387
- });
194
+ if (session.status === "updated") {
195
+ updateCompiledWorkflowOrigin({
196
+ compiledPath: options.compiledPath,
197
+ selectedWorkflowId: options.workflowId,
198
+ localDraft: true,
199
+ });
200
+ }
388
201
  return {
389
- status: "updated",
390
- changed: true,
391
- validation,
392
- summary: "Workflow variation updated and validated.",
202
+ status: session.status,
203
+ changed: session.changed,
204
+ validation: session.validation,
205
+ summary: session.summary,
393
206
  shellPath: shell.rootPath,
394
207
  loopRootPath: shell.loopRootPath,
395
208
  };
@@ -0,0 +1,2 @@
1
+ export declare const COMPILED_ZONE_KINDS: readonly ["directory", "file", "runtime"];
2
+ export type CompiledZoneKind = typeof COMPILED_ZONE_KINDS[number];