@workflow-cannon/workspace-kit 0.17.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +23 -9
  2. package/dist/cli/doctor-planning-issues.js +3 -22
  3. package/dist/cli/run-command.js +22 -38
  4. package/dist/cli.js +95 -4
  5. package/dist/contracts/command-manifest.d.ts +17 -0
  6. package/dist/contracts/command-manifest.js +1 -0
  7. package/dist/contracts/index.d.ts +1 -1
  8. package/dist/contracts/module-contract.d.ts +12 -11
  9. package/dist/core/agent-instruction-surface.d.ts +33 -0
  10. package/dist/core/agent-instruction-surface.js +46 -0
  11. package/dist/core/config-cli.js +13 -17
  12. package/dist/core/config-metadata.js +101 -2
  13. package/dist/core/index.d.ts +4 -1
  14. package/dist/core/index.js +3 -0
  15. package/dist/core/module-command-router.js +19 -1
  16. package/dist/core/module-registry-resolve.d.ts +27 -0
  17. package/dist/core/module-registry-resolve.js +91 -0
  18. package/dist/core/module-registry.d.ts +14 -0
  19. package/dist/core/module-registry.js +57 -0
  20. package/dist/core/planning/build-plan-session-file.d.ts +29 -0
  21. package/dist/core/planning/build-plan-session-file.js +58 -0
  22. package/dist/core/planning/index.d.ts +17 -0
  23. package/dist/core/planning/index.js +15 -0
  24. package/dist/core/policy.js +18 -8
  25. package/dist/core/state/unified-state-db.d.ts +21 -0
  26. package/dist/core/state/unified-state-db.js +80 -0
  27. package/dist/core/workspace-kit-config.js +13 -1
  28. package/dist/modules/agent-behavior/builtins.d.ts +3 -0
  29. package/dist/modules/agent-behavior/builtins.js +71 -0
  30. package/dist/modules/agent-behavior/explain.d.ts +6 -0
  31. package/dist/modules/agent-behavior/explain.js +46 -0
  32. package/dist/modules/agent-behavior/index.d.ts +4 -0
  33. package/dist/modules/agent-behavior/index.js +461 -0
  34. package/dist/modules/agent-behavior/interview-session-file.d.ts +9 -0
  35. package/dist/modules/agent-behavior/interview-session-file.js +43 -0
  36. package/dist/modules/agent-behavior/interview.d.ts +13 -0
  37. package/dist/modules/agent-behavior/interview.js +88 -0
  38. package/dist/modules/agent-behavior/persistence.d.ts +6 -0
  39. package/dist/modules/agent-behavior/persistence.js +89 -0
  40. package/dist/modules/agent-behavior/store.d.ts +34 -0
  41. package/dist/modules/agent-behavior/store.js +119 -0
  42. package/dist/modules/agent-behavior/types.d.ts +28 -0
  43. package/dist/modules/agent-behavior/types.js +1 -0
  44. package/dist/modules/agent-behavior/validate.d.ts +11 -0
  45. package/dist/modules/agent-behavior/validate.js +123 -0
  46. package/dist/modules/approvals/index.js +54 -51
  47. package/dist/modules/approvals/policy-sensitive-commands.d.ts +4 -0
  48. package/dist/modules/approvals/policy-sensitive-commands.js +4 -0
  49. package/dist/modules/approvals/review-runtime.js +1 -2
  50. package/dist/modules/documentation/index.js +47 -45
  51. package/dist/modules/documentation/normalizer.d.ts +3 -0
  52. package/dist/modules/documentation/normalizer.js +171 -0
  53. package/dist/modules/documentation/parser.d.ts +7 -0
  54. package/dist/modules/documentation/parser.js +39 -0
  55. package/dist/modules/documentation/policy-sensitive-commands.d.ts +5 -0
  56. package/dist/modules/documentation/policy-sensitive-commands.js +8 -0
  57. package/dist/modules/documentation/renderer.d.ts +23 -0
  58. package/dist/modules/documentation/renderer.js +105 -0
  59. package/dist/modules/documentation/runtime-batch.d.ts +10 -0
  60. package/dist/modules/documentation/runtime-batch.js +67 -0
  61. package/dist/modules/documentation/runtime-config.d.ts +11 -0
  62. package/dist/modules/documentation/runtime-config.js +54 -0
  63. package/dist/modules/documentation/runtime-render-support.d.ts +8 -0
  64. package/dist/modules/documentation/runtime-render-support.js +36 -0
  65. package/dist/modules/documentation/runtime.js +22 -510
  66. package/dist/modules/documentation/types.d.ts +182 -0
  67. package/dist/modules/documentation/validator.d.ts +8 -0
  68. package/dist/modules/documentation/validator.js +234 -0
  69. package/dist/modules/documentation/view-models.d.ts +3 -0
  70. package/dist/modules/documentation/view-models.js +124 -0
  71. package/dist/modules/improvement/generate-recommendations-runtime.js +3 -3
  72. package/dist/modules/improvement/improvement-state.d.ts +2 -2
  73. package/dist/modules/improvement/improvement-state.js +52 -23
  74. package/dist/modules/improvement/index.js +140 -138
  75. package/dist/modules/improvement/ingest.d.ts +1 -1
  76. package/dist/modules/improvement/policy-sensitive-commands.d.ts +4 -0
  77. package/dist/modules/improvement/policy-sensitive-commands.js +7 -0
  78. package/dist/modules/index.d.ts +6 -0
  79. package/dist/modules/index.js +17 -0
  80. package/dist/modules/planning/artifact.d.ts +19 -0
  81. package/dist/modules/planning/artifact.js +72 -0
  82. package/dist/modules/planning/index.js +605 -6
  83. package/dist/modules/planning/question-engine.d.ts +25 -0
  84. package/dist/modules/planning/question-engine.js +284 -0
  85. package/dist/modules/planning/types.d.ts +9 -0
  86. package/dist/modules/planning/types.js +39 -0
  87. package/dist/modules/task-engine/doctor-planning-persistence.js +21 -13
  88. package/dist/modules/task-engine/index.d.ts +1 -2
  89. package/dist/modules/task-engine/index.js +1 -1143
  90. package/dist/modules/task-engine/migrate-task-persistence-runtime.js +31 -4
  91. package/dist/modules/task-engine/migrate-wishlist-intake-runtime.d.ts +2 -0
  92. package/dist/modules/task-engine/migrate-wishlist-intake-runtime.js +146 -0
  93. package/dist/modules/task-engine/planning-open.d.ts +2 -9
  94. package/dist/modules/task-engine/planning-open.js +4 -15
  95. package/dist/modules/task-engine/policy-sensitive-commands.d.ts +5 -0
  96. package/dist/modules/task-engine/policy-sensitive-commands.js +5 -0
  97. package/dist/modules/task-engine/sqlite-dual-planning.d.ts +11 -2
  98. package/dist/modules/task-engine/sqlite-dual-planning.js +134 -28
  99. package/dist/modules/task-engine/strict-task-validation.js +3 -0
  100. package/dist/modules/task-engine/suggestions.js +2 -1
  101. package/dist/modules/task-engine/task-engine-internal.d.ts +2 -0
  102. package/dist/modules/task-engine/task-engine-internal.js +1304 -0
  103. package/dist/modules/task-engine/task-type-validation.js +40 -0
  104. package/dist/modules/task-engine/wishlist-intake.d.ts +22 -0
  105. package/dist/modules/task-engine/wishlist-intake.js +180 -0
  106. package/dist/modules/task-engine/wishlist-validation.d.ts +4 -0
  107. package/dist/modules/task-engine/wishlist-validation.js +19 -0
  108. package/dist/modules/workspace-config/index.js +9 -11
  109. package/package.json +2 -2
  110. package/schemas/agent-behavior-profile.schema.json +52 -0
  111. package/schemas/task-engine-run-contracts.schema.json +80 -5
  112. package/src/modules/documentation/README.md +16 -25
  113. package/src/modules/documentation/RULES.md +9 -9
  114. package/src/modules/documentation/index.ts +54 -49
  115. package/src/modules/documentation/instructions/document-project.md +6 -6
  116. package/src/modules/documentation/instructions/generate-document.md +4 -4
  117. package/src/modules/documentation/normalizer.ts +187 -0
  118. package/src/modules/documentation/parser.ts +41 -0
  119. package/src/modules/documentation/policy-sensitive-commands.ts +8 -0
  120. package/src/modules/documentation/renderer.ts +121 -0
  121. package/src/modules/documentation/runtime-batch.ts +74 -0
  122. package/src/modules/documentation/runtime-config.ts +68 -0
  123. package/src/modules/documentation/runtime-render-support.ts +39 -0
  124. package/src/modules/documentation/runtime.ts +28 -600
  125. package/src/modules/documentation/schemas/documentation-schema.md +37 -54
  126. package/src/modules/documentation/types.ts +228 -0
  127. package/src/modules/documentation/validator.ts +247 -0
  128. package/src/modules/documentation/view-models.ts +132 -0
  129. package/src/modules/documentation/views/agents.view.yaml +18 -0
  130. package/src/modules/documentation/views/architecture.view.yaml +18 -0
  131. package/src/modules/documentation/views/principles.view.yaml +18 -0
  132. package/src/modules/documentation/views/readme.view.yaml +18 -0
  133. package/src/modules/documentation/views/releasing.view.yaml +18 -0
  134. package/src/modules/documentation/views/roadmap.view.yaml +18 -0
  135. package/src/modules/documentation/views/runbooks-consumer-cadence.view.yaml +18 -0
  136. package/src/modules/documentation/views/runbooks-parity-validation-flow.view.yaml +18 -0
  137. package/src/modules/documentation/views/runbooks-release-channels.view.yaml +18 -0
  138. package/src/modules/documentation/views/security.view.yaml +18 -0
  139. package/src/modules/documentation/views/support.view.yaml +18 -0
  140. package/src/modules/documentation/views/terms.view.yaml +18 -0
  141. package/src/modules/documentation/views/workbooks-phase2-config-policy-workbook.view.yaml +18 -0
  142. package/src/modules/documentation/views/workbooks-task-engine-workbook.view.yaml +18 -0
  143. package/src/modules/documentation/views/workbooks-transcript-automation-baseline.view.yaml +18 -0
  144. package/src/modules/documentation/state.md +0 -8
@@ -69,6 +69,86 @@ const REGISTRY = {
69
69
  exposure: "public",
70
70
  writableLayers: ["project", "user"]
71
71
  },
72
+ "modules.enabled": {
73
+ key: "modules.enabled",
74
+ type: "array",
75
+ description: "When non-empty, only these module ids are enabled (whitelist); then modules.disabled subtracts. When empty, all modules use registration.enabledByDefault.",
76
+ default: [],
77
+ domainScope: "project",
78
+ owningModule: "workspace-kit",
79
+ sensitive: false,
80
+ requiresRestart: false,
81
+ requiresApproval: false,
82
+ exposure: "maintainer",
83
+ writableLayers: ["project", "user"]
84
+ },
85
+ "modules.disabled": {
86
+ key: "modules.disabled",
87
+ type: "array",
88
+ description: "Module ids to disable after computing the candidate enabled set (default-by-flag or modules.enabled whitelist).",
89
+ default: [],
90
+ domainScope: "project",
91
+ owningModule: "workspace-kit",
92
+ sensitive: false,
93
+ requiresRestart: false,
94
+ requiresApproval: false,
95
+ exposure: "maintainer",
96
+ writableLayers: ["project", "user"]
97
+ },
98
+ "planning.defaultQuestionDepth": {
99
+ key: "planning.defaultQuestionDepth",
100
+ type: "string",
101
+ description: "Planning interview depth mode: minimal (critical only), guided (critical + static follow-ups), or adaptive (context-driven follow-ups).",
102
+ default: "adaptive",
103
+ allowedValues: ["minimal", "guided", "adaptive"],
104
+ domainScope: "project",
105
+ owningModule: "planning",
106
+ sensitive: false,
107
+ requiresRestart: false,
108
+ requiresApproval: false,
109
+ exposure: "maintainer",
110
+ writableLayers: ["project", "user"]
111
+ },
112
+ "planning.hardBlockCriticalUnknowns": {
113
+ key: "planning.hardBlockCriticalUnknowns",
114
+ type: "boolean",
115
+ description: "When true, planning finalize requests fail until critical unknown questions are answered.",
116
+ default: true,
117
+ domainScope: "project",
118
+ owningModule: "planning",
119
+ sensitive: false,
120
+ requiresRestart: false,
121
+ requiresApproval: false,
122
+ exposure: "maintainer",
123
+ writableLayers: ["project", "user"]
124
+ },
125
+ "planning.adaptiveFinalizePolicy": {
126
+ key: "planning.adaptiveFinalizePolicy",
127
+ type: "string",
128
+ description: "Controls finalize handling for unresolved adaptive follow-up questions: off (ignore), warn (allow finalize with warnings), block (deny finalize).",
129
+ default: "off",
130
+ allowedValues: ["off", "warn", "block"],
131
+ domainScope: "project",
132
+ owningModule: "planning",
133
+ sensitive: false,
134
+ requiresRestart: false,
135
+ requiresApproval: false,
136
+ exposure: "maintainer",
137
+ writableLayers: ["project", "user"]
138
+ },
139
+ "planning.rulePacks": {
140
+ key: "planning.rulePacks",
141
+ type: "object",
142
+ description: "Optional object overrides for planning rule packs by workflow type (`baseQuestions` and `adaptiveQuestions`).",
143
+ default: {},
144
+ domainScope: "project",
145
+ owningModule: "planning",
146
+ sensitive: false,
147
+ requiresRestart: false,
148
+ requiresApproval: false,
149
+ exposure: "maintainer",
150
+ writableLayers: ["project", "user"]
151
+ },
72
152
  "policy.extraSensitiveModuleCommands": {
73
153
  key: "policy.extraSensitiveModuleCommands",
74
154
  type: "array",
@@ -299,6 +379,13 @@ export function validateValueForMetadata(meta, value) {
299
379
  }
300
380
  }
301
381
  }
382
+ if (meta.key === "modules.enabled" || meta.key === "modules.disabled") {
383
+ for (const item of value) {
384
+ if (typeof item !== "string" || item.trim().length === 0) {
385
+ throw new Error(`config-type-error(${meta.key}): array entries must be non-empty strings`);
386
+ }
387
+ }
388
+ }
302
389
  return;
303
390
  }
304
391
  if (meta.type === "string" && typeof value !== "string") {
@@ -417,8 +504,20 @@ export function validatePersistedConfigDocument(data, label) {
417
504
  }
418
505
  const mods = data.modules;
419
506
  if (mods !== undefined) {
420
- if (typeof mods !== "object" || mods === null || Array.isArray(mods) || Object.keys(mods).length > 0) {
421
- throw new Error(`config-invalid(${label}): modules must be absent or an empty object`);
507
+ if (typeof mods !== "object" || mods === null || Array.isArray(mods)) {
508
+ throw new Error(`config-invalid(${label}): modules must be an object`);
509
+ }
510
+ const m = mods;
511
+ for (const k of Object.keys(m)) {
512
+ if (k !== "enabled" && k !== "disabled") {
513
+ throw new Error(`config-invalid(${label}): unknown modules.${k}`);
514
+ }
515
+ }
516
+ if (m.enabled !== undefined) {
517
+ validateValueForMetadata(REGISTRY["modules.enabled"], m.enabled);
518
+ }
519
+ if (m.disabled !== undefined) {
520
+ validateValueForMetadata(REGISTRY["modules.disabled"], m.disabled);
422
521
  }
423
522
  }
424
523
  const improvement = data.improvement;
@@ -1,5 +1,7 @@
1
- export { ModuleRegistry, ModuleRegistryError, validateModuleSet, type ModuleRegistryOptions } from "./module-registry.js";
1
+ export { ModuleRegistry, ModuleRegistryError, validateModuleSet, type ModuleActivationEntry, type ModuleActivationReport, type ModuleRegistryOptions } from "./module-registry.js";
2
+ export { moduleRegistryOptionsFromEffectiveConfig, pickModuleContractWorkspacePath, resolveRegistryAndConfig } from "./module-registry-resolve.js";
2
3
  export { ModuleCommandRouter, ModuleCommandRouterError, type ModuleCommandDescriptor, type ModuleCommandRouterOptions } from "./module-command-router.js";
4
+ export { buildAgentInstructionSurface, classifyInstructionExecution, isInstructionExecutableForRegistry, type AgentInstructionDegradation, type AgentInstructionSurfacePayload, type AgentInstructionSurfaceRow } from "./agent-instruction-surface.js";
3
5
  export { buildBaseConfigLayers, deepMerge, envToConfigOverlay, explainConfigPath, getAtPath, getProjectConfigPath, getUserConfigFilePath, KIT_CONFIG_DEFAULTS, loadUserLayer, mergeConfigLayers, MODULE_CONFIG_CONTRIBUTIONS, normalizeConfigForExport, PROJECT_CONFIG_REL, resolveWorkspaceConfigWithLayers, stableStringifyConfig, type ConfigLayer, type ConfigLayerId, type EffectiveWorkspaceConfig, type ExplainConfigResult, type ResolveWorkspaceConfigOptions } from "./workspace-kit-config.js";
4
6
  export { appendPolicyTrace, getExtraSensitiveModuleCommandsFromEffective, getOperationIdForCommand, isSensitiveModuleCommand, isSensitiveModuleCommandForEffective, parsePolicyApproval, parsePolicyApprovalFromEnv, POLICY_APPROVAL_HUMAN_DOC, POLICY_TRACE_SCHEMA_VERSION, resolveActor, resolvePolicyOperationIdForCommand, type PolicyOperationId, type PolicyTraceRecord, type PolicyTraceRecordInput } from "./policy.js";
5
7
  export { getSessionGrant, loadSessionPolicyDocument, recordSessionGrant, resolveSessionId, SESSION_POLICY_SCHEMA_VERSION, type SessionPolicyDocument, type SessionPolicyGrant } from "./session-policy.js";
@@ -13,4 +15,5 @@ export { appendConfigMutation, CONFIG_MUTATIONS_SCHEMA_VERSION, summarizeForEvid
13
15
  export { generateConfigReferenceDocs, runWorkspaceConfigCli, type ConfigCliIo } from "./config-cli.js";
14
16
  export { LINEAGE_SCHEMA_VERSION, lineageCorrelationRoot, type LineageAppPayload, type LineageCorrPayload, type LineageDecPayload, type LineageEvent, type LineageEventType, type LineageRecPayload } from "./lineage-contract.js";
15
17
  export { appendLineageEvent, newLineageEventId, queryLineageChain, readLineageEvents } from "./lineage-store.js";
18
+ export { UnifiedStateDb, type ModuleStateRow } from "./state/unified-state-db.js";
16
19
  export type CoreRuntimeVersion = "0.1";
@@ -1,5 +1,7 @@
1
1
  export { ModuleRegistry, ModuleRegistryError, validateModuleSet } from "./module-registry.js";
2
+ export { moduleRegistryOptionsFromEffectiveConfig, pickModuleContractWorkspacePath, resolveRegistryAndConfig } from "./module-registry-resolve.js";
2
3
  export { ModuleCommandRouter, ModuleCommandRouterError } from "./module-command-router.js";
4
+ export { buildAgentInstructionSurface, classifyInstructionExecution, isInstructionExecutableForRegistry } from "./agent-instruction-surface.js";
3
5
  export { buildBaseConfigLayers, deepMerge, envToConfigOverlay, explainConfigPath, getAtPath, getProjectConfigPath, getUserConfigFilePath, KIT_CONFIG_DEFAULTS, loadUserLayer, mergeConfigLayers, MODULE_CONFIG_CONTRIBUTIONS, normalizeConfigForExport, PROJECT_CONFIG_REL, resolveWorkspaceConfigWithLayers, stableStringifyConfig } from "./workspace-kit-config.js";
4
6
  export { appendPolicyTrace, getExtraSensitiveModuleCommandsFromEffective, getOperationIdForCommand, isSensitiveModuleCommand, isSensitiveModuleCommandForEffective, parsePolicyApproval, parsePolicyApprovalFromEnv, POLICY_APPROVAL_HUMAN_DOC, POLICY_TRACE_SCHEMA_VERSION, resolveActor, resolvePolicyOperationIdForCommand } from "./policy.js";
5
7
  export { getSessionGrant, loadSessionPolicyDocument, recordSessionGrant, resolveSessionId, SESSION_POLICY_SCHEMA_VERSION } from "./session-policy.js";
@@ -13,3 +15,4 @@ export { appendConfigMutation, CONFIG_MUTATIONS_SCHEMA_VERSION, summarizeForEvid
13
15
  export { generateConfigReferenceDocs, runWorkspaceConfigCli } from "./config-cli.js";
14
16
  export { LINEAGE_SCHEMA_VERSION, lineageCorrelationRoot } from "./lineage-contract.js";
15
17
  export { appendLineageEvent, newLineageEventId, queryLineageChain, readLineageEvents } from "./lineage-store.js";
18
+ export { UnifiedStateDb } from "./state/unified-state-db.js";
@@ -1,3 +1,4 @@
1
+ import { classifyInstructionExecution, isInstructionExecutableForRegistry } from "./agent-instruction-surface.js";
1
2
  export class ModuleCommandRouterError extends Error {
2
3
  code;
3
4
  constructor(code, message) {
@@ -40,6 +41,19 @@ export class ModuleCommandRouter {
40
41
  if (!indexed.module.onCommand) {
41
42
  throw new ModuleCommandRouterError("command-not-implemented", `Module '${indexed.descriptor.moduleId}' does not implement onCommand for '${commandName}'`);
42
43
  }
44
+ if (!isInstructionExecutableForRegistry(indexed.module, indexed.entry, this.registry)) {
45
+ const deg = classifyInstructionExecution(indexed.module, indexed.entry, this.registry);
46
+ const detail = deg.kind === "peer_disabled"
47
+ ? `missing peers: ${deg.missingPeers.join(", ")}`
48
+ : deg.kind === "module_disabled"
49
+ ? "owning module disabled"
50
+ : "not executable";
51
+ return {
52
+ ok: false,
53
+ code: "peer-module-disabled",
54
+ message: `Command '${commandName}' is not executable (${detail}). Enable required modules or follow the instruction markdown for manual guidance. Instruction: ${indexed.descriptor.instructionFile}`
55
+ };
56
+ }
43
57
  const command = {
44
58
  name: commandName,
45
59
  args
@@ -49,6 +63,9 @@ export class ModuleCommandRouter {
49
63
  indexEnabledModuleCommands() {
50
64
  for (const module of this.registry.getEnabledModules()) {
51
65
  for (const entry of module.registration.instructions.entries) {
66
+ if (!isInstructionExecutableForRegistry(module, entry, this.registry)) {
67
+ continue;
68
+ }
52
69
  if (this.commands.has(entry.name)) {
53
70
  const existing = this.commands.get(entry.name);
54
71
  throw new ModuleCommandRouterError("duplicate-command", `Command '${entry.name}' is declared by both '${existing?.descriptor.moduleId}' and '${module.registration.id}'`);
@@ -60,7 +77,8 @@ export class ModuleCommandRouter {
60
77
  instructionFile: `${module.registration.instructions.directory}/${entry.file}`,
61
78
  description: entry.description
62
79
  },
63
- module
80
+ module,
81
+ entry
64
82
  });
65
83
  }
66
84
  }
@@ -0,0 +1,27 @@
1
+ import type { WorkflowModule } from "../contracts/module-contract.js";
2
+ import { ModuleRegistry, type ModuleRegistryOptions } from "./module-registry.js";
3
+ import { type ConfigLayer, type EffectiveWorkspaceConfig } from "./workspace-kit-config.js";
4
+ /**
5
+ * Instruction paths in module registration are repo-relative. Use `workspacePath` when it
6
+ * contains the kit sources; otherwise fall back to `process.cwd()` (tests / ephemeral cwd).
7
+ */
8
+ export declare function pickModuleContractWorkspacePath(workspacePath: string): string;
9
+ /**
10
+ * Reads `modules.enabled` / `modules.disabled` from effective config and maps them
11
+ * to ModuleRegistryOptions. Unknown module ids throw — fail fast on typos.
12
+ *
13
+ * Semantics (matches resolveEnabledModuleIds):
14
+ * - If `modules.enabled` is non-empty: only those ids are candidates, then `modules.disabled` subtracts.
15
+ * - If `modules.enabled` is empty/absent: start from each module's enabledByDefault, then subtract disabled.
16
+ */
17
+ export declare function moduleRegistryOptionsFromEffectiveConfig(effective: Record<string, unknown>, knownModuleIds: Set<string>): Pick<ModuleRegistryOptions, "enabledModules" | "disabledModules">;
18
+ /**
19
+ * Resolves layered config together with module enablement toggles from that config.
20
+ * Iterates until the enabled module set stabilizes (module config contributions can
21
+ * change when modules drop out of startup order).
22
+ */
23
+ export declare function resolveRegistryAndConfig(workspacePath: string, allModules: WorkflowModule[], invocationConfig?: Record<string, unknown>): Promise<{
24
+ registry: ModuleRegistry;
25
+ effective: EffectiveWorkspaceConfig;
26
+ layers: ConfigLayer[];
27
+ }>;
@@ -0,0 +1,91 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { ModuleRegistry, ModuleRegistryError } from "./module-registry.js";
4
+ import { resolveWorkspaceConfigWithLayers } from "./workspace-kit-config.js";
5
+ /**
6
+ * Instruction paths in module registration are repo-relative. Use `workspacePath` when it
7
+ * contains the kit sources; otherwise fall back to `process.cwd()` (tests / ephemeral cwd).
8
+ */
9
+ export function pickModuleContractWorkspacePath(workspacePath) {
10
+ const marker = resolve(workspacePath, "src/modules/task-engine/config.md");
11
+ if (existsSync(marker)) {
12
+ return workspacePath;
13
+ }
14
+ return process.cwd();
15
+ }
16
+ function readNonEmptyStringArray(value) {
17
+ if (!Array.isArray(value)) {
18
+ return undefined;
19
+ }
20
+ const out = value.filter((x) => typeof x === "string" && x.trim().length > 0);
21
+ return out.length > 0 ? out : undefined;
22
+ }
23
+ /**
24
+ * Reads `modules.enabled` / `modules.disabled` from effective config and maps them
25
+ * to ModuleRegistryOptions. Unknown module ids throw — fail fast on typos.
26
+ *
27
+ * Semantics (matches resolveEnabledModuleIds):
28
+ * - If `modules.enabled` is non-empty: only those ids are candidates, then `modules.disabled` subtracts.
29
+ * - If `modules.enabled` is empty/absent: start from each module's enabledByDefault, then subtract disabled.
30
+ */
31
+ export function moduleRegistryOptionsFromEffectiveConfig(effective, knownModuleIds) {
32
+ const root = effective.modules;
33
+ if (root === undefined || root === null) {
34
+ return {};
35
+ }
36
+ if (typeof root !== "object" || Array.isArray(root)) {
37
+ throw new ModuleRegistryError("invalid-modules-config", "effectiveConfig.modules must be an object when present");
38
+ }
39
+ const mod = root;
40
+ const enabled = readNonEmptyStringArray(mod.enabled);
41
+ const disabled = readNonEmptyStringArray(mod.disabled);
42
+ for (const id of [...(enabled ?? []), ...(disabled ?? [])]) {
43
+ if (!knownModuleIds.has(id)) {
44
+ throw new ModuleRegistryError("unknown-module-in-config", `Unknown module id in modules.enabled / modules.disabled: '${id}'`);
45
+ }
46
+ }
47
+ return {
48
+ enabledModules: enabled,
49
+ disabledModules: disabled
50
+ };
51
+ }
52
+ function enabledSignature(registry) {
53
+ return registry
54
+ .getEnabledModules()
55
+ .map((m) => m.registration.id)
56
+ .sort()
57
+ .join(",");
58
+ }
59
+ /**
60
+ * Resolves layered config together with module enablement toggles from that config.
61
+ * Iterates until the enabled module set stabilizes (module config contributions can
62
+ * change when modules drop out of startup order).
63
+ */
64
+ export async function resolveRegistryAndConfig(workspacePath, allModules, invocationConfig) {
65
+ const knownIds = new Set(allModules.map((m) => m.registration.id));
66
+ const moduleContractPath = pickModuleContractWorkspacePath(workspacePath);
67
+ let registry = new ModuleRegistry(allModules, { workspacePath: moduleContractPath });
68
+ for (let i = 0; i < 8; i++) {
69
+ const { effective, layers } = await resolveWorkspaceConfigWithLayers({
70
+ workspacePath,
71
+ registry,
72
+ invocationConfig
73
+ });
74
+ const extra = moduleRegistryOptionsFromEffectiveConfig(effective, knownIds);
75
+ const candidate = new ModuleRegistry(allModules, { workspacePath: moduleContractPath, ...extra });
76
+ if (enabledSignature(registry) === enabledSignature(candidate)) {
77
+ const fin = await resolveWorkspaceConfigWithLayers({
78
+ workspacePath,
79
+ registry: candidate,
80
+ invocationConfig
81
+ });
82
+ return {
83
+ registry: candidate,
84
+ effective: fin.effective,
85
+ layers: fin.layers
86
+ };
87
+ }
88
+ registry = candidate;
89
+ }
90
+ throw new ModuleRegistryError("module-enablement-unstable", "modules.enabled / modules.disabled did not stabilize after repeated config resolution; check config layers");
91
+ }
@@ -3,6 +3,18 @@ export declare class ModuleRegistryError extends Error {
3
3
  readonly code: string;
4
4
  constructor(code: string, message: string);
5
5
  }
6
+ export type ModuleActivationEntry = {
7
+ moduleId: string;
8
+ enabled: boolean;
9
+ /** dependsOn entries not present in the enabled set (non-empty only if misconfigured). */
10
+ unsatisfiedHardDependencies: string[];
11
+ /** optionalPeers not currently enabled (informational). */
12
+ missingOptionalPeers: string[];
13
+ };
14
+ export type ModuleActivationReport = {
15
+ schemaVersion: 1;
16
+ modules: ModuleActivationEntry[];
17
+ };
6
18
  export declare function validateModuleSet(modules: WorkflowModule[], workspacePath?: string): void;
7
19
  export type ModuleRegistryOptions = {
8
20
  enabledModules?: string[];
@@ -21,4 +33,6 @@ export declare class ModuleRegistry {
21
33
  isModuleEnabled(id: string): boolean;
22
34
  getEnabledModules(): WorkflowModule[];
23
35
  getStartupOrder(): WorkflowModule[];
36
+ /** Snapshot for doctor / tooling: enablement and peer satisfaction. */
37
+ getActivationReport(): ModuleActivationReport;
24
38
  }
@@ -32,6 +32,32 @@ function validateDependencies(moduleMap) {
32
32
  }
33
33
  }
34
34
  }
35
+ function validateOptionalPeers(moduleMap) {
36
+ for (const module of moduleMap.values()) {
37
+ const moduleId = module.registration.id;
38
+ const peers = module.registration.optionalPeers ?? [];
39
+ const hard = new Set(module.registration.dependsOn);
40
+ for (const peerId of peers) {
41
+ if (peerId === moduleId) {
42
+ throw new ModuleRegistryError("self-optional-peer", `Module '${moduleId}' cannot list itself in optionalPeers`);
43
+ }
44
+ if (!moduleMap.has(peerId)) {
45
+ throw new ModuleRegistryError("missing-optional-peer", `Module '${moduleId}' lists unknown optional peer '${peerId}'`);
46
+ }
47
+ if (hard.has(peerId)) {
48
+ throw new ModuleRegistryError("optional-peer-overlap-dependsOn", `Module '${moduleId}' lists '${peerId}' in both dependsOn and optionalPeers`);
49
+ }
50
+ }
51
+ }
52
+ }
53
+ function validateRegistrationSchemas(moduleMap) {
54
+ for (const module of moduleMap.values()) {
55
+ const schema = module.registration.stateSchema;
56
+ if (!Number.isInteger(schema) || schema < 1) {
57
+ throw new ModuleRegistryError("invalid-state-schema", `Module '${module.registration.id}' must declare integer stateSchema >= 1`);
58
+ }
59
+ }
60
+ }
35
61
  function topologicalSort(moduleMap) {
36
62
  const visited = new Set();
37
63
  const inStack = new Set();
@@ -139,12 +165,23 @@ function validateInstructionContracts(moduleMap, workspacePath) {
139
165
  if (!statSync(instructionFilePath).isFile()) {
140
166
  throw new ModuleRegistryError("invalid-instruction-file", `Module '${id}' instruction path '${instructionFilePath}' is not a file`);
141
167
  }
168
+ const reqPeers = entry.requiresPeers ?? [];
169
+ for (const peerId of reqPeers) {
170
+ if (peerId === id) {
171
+ throw new ModuleRegistryError("instruction-requires-self", `Module '${id}' instruction '${entry.name}' cannot list its own module id in requiresPeers`);
172
+ }
173
+ if (!moduleMap.has(peerId)) {
174
+ throw new ModuleRegistryError("unknown-requires-peer", `Module '${id}' instruction '${entry.name}' lists unknown requiresPeers module '${peerId}'`);
175
+ }
176
+ }
142
177
  }
143
178
  }
144
179
  }
145
180
  export function validateModuleSet(modules, workspacePath) {
146
181
  const moduleMap = buildModuleMap(modules);
147
182
  validateDependencies(moduleMap);
183
+ validateOptionalPeers(moduleMap);
184
+ validateRegistrationSchemas(moduleMap);
148
185
  validateInstructionContracts(moduleMap, workspacePath ?? process.cwd());
149
186
  topologicalSort(moduleMap);
150
187
  }
@@ -157,6 +194,8 @@ export class ModuleRegistry {
157
194
  constructor(modules, options) {
158
195
  this.moduleMap = buildModuleMap(modules);
159
196
  validateDependencies(this.moduleMap);
197
+ validateOptionalPeers(this.moduleMap);
198
+ validateRegistrationSchemas(this.moduleMap);
160
199
  validateInstructionContracts(this.moduleMap, options?.workspacePath ?? process.cwd());
161
200
  this.modules = [...modules];
162
201
  const enabledModuleIds = resolveEnabledModuleIds(this.modules, options);
@@ -180,4 +219,22 @@ export class ModuleRegistry {
180
219
  getStartupOrder() {
181
220
  return [...this.sortedModules];
182
221
  }
222
+ /** Snapshot for doctor / tooling: enablement and peer satisfaction. */
223
+ getActivationReport() {
224
+ const enabledIds = new Set(this.enabledModuleMap.keys());
225
+ const modules = [];
226
+ for (const mod of this.modules) {
227
+ const id = mod.registration.id;
228
+ const optionalPeers = mod.registration.optionalPeers ?? [];
229
+ const missingOptionalPeers = optionalPeers.filter((p) => !enabledIds.has(p));
230
+ const unsatisfiedHardDependencies = mod.registration.dependsOn.filter((d) => !enabledIds.has(d));
231
+ modules.push({
232
+ moduleId: id,
233
+ enabled: enabledIds.has(id),
234
+ unsatisfiedHardDependencies,
235
+ missingOptionalPeers
236
+ });
237
+ }
238
+ return { schemaVersion: 1, modules };
239
+ }
183
240
  }
@@ -0,0 +1,29 @@
1
+ /** Local operator snapshot so dashboards and agents can resume `build-plan` without re-entering answers. */
2
+ export type BuildPlanSessionSnapshotV1 = {
3
+ schemaVersion: 1;
4
+ updatedAt: string;
5
+ planningType: string;
6
+ outputMode: string;
7
+ status: string;
8
+ completionPct: number;
9
+ answeredCritical: number;
10
+ totalCritical: number;
11
+ answers: Record<string, unknown>;
12
+ /** Single-line `workspace-kit run build-plan '…'` hint (shell-escaped JSON inside quotes is caller responsibility). */
13
+ resumeCli: string;
14
+ };
15
+ export type DashboardPlanningSessionV1 = {
16
+ schemaVersion: 1;
17
+ updatedAt: string;
18
+ planningType: string;
19
+ outputMode: string;
20
+ status: string;
21
+ completionPct: number;
22
+ answeredCritical: number;
23
+ totalCritical: number;
24
+ resumeCli: string;
25
+ };
26
+ export declare function persistBuildPlanSession(workspacePath: string, snapshot: Omit<BuildPlanSessionSnapshotV1, "schemaVersion" | "updatedAt">): Promise<void>;
27
+ export declare function clearBuildPlanSession(workspacePath: string): Promise<void>;
28
+ export declare function readBuildPlanSession(workspacePath: string): Promise<BuildPlanSessionSnapshotV1 | null>;
29
+ export declare function toDashboardPlanningSession(snap: BuildPlanSessionSnapshotV1 | null): DashboardPlanningSessionV1 | null;
@@ -0,0 +1,58 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const REL_DIR = path.join(".workspace-kit", "planning");
4
+ const FILE_NAME = "build-plan-session.json";
5
+ function sessionPath(workspacePath) {
6
+ return path.join(workspacePath, REL_DIR, FILE_NAME);
7
+ }
8
+ export async function persistBuildPlanSession(workspacePath, snapshot) {
9
+ const dir = path.join(workspacePath, REL_DIR);
10
+ await fs.mkdir(dir, { recursive: true });
11
+ const full = {
12
+ schemaVersion: 1,
13
+ updatedAt: new Date().toISOString(),
14
+ ...snapshot
15
+ };
16
+ await fs.writeFile(sessionPath(workspacePath), `${JSON.stringify(full, null, 2)}\n`, "utf8");
17
+ }
18
+ export async function clearBuildPlanSession(workspacePath) {
19
+ try {
20
+ await fs.unlink(sessionPath(workspacePath));
21
+ }
22
+ catch (err) {
23
+ const code = err.code;
24
+ if (code !== "ENOENT")
25
+ throw err;
26
+ }
27
+ }
28
+ export async function readBuildPlanSession(workspacePath) {
29
+ try {
30
+ const raw = await fs.readFile(sessionPath(workspacePath), "utf8");
31
+ const parsed = JSON.parse(raw);
32
+ if (parsed?.schemaVersion !== 1 || typeof parsed.planningType !== "string") {
33
+ return null;
34
+ }
35
+ if (typeof parsed.resumeCli !== "string") {
36
+ return null;
37
+ }
38
+ return parsed;
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ export function toDashboardPlanningSession(snap) {
45
+ if (!snap)
46
+ return null;
47
+ return {
48
+ schemaVersion: 1,
49
+ updatedAt: snap.updatedAt,
50
+ planningType: snap.planningType,
51
+ outputMode: snap.outputMode,
52
+ status: snap.status,
53
+ completionPct: snap.completionPct,
54
+ answeredCritical: snap.answeredCritical,
55
+ totalCritical: snap.totalCritical,
56
+ resumeCli: snap.resumeCli
57
+ };
58
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shared planning-domain exports for cross-module consumers.
3
+ *
4
+ * **Planning persistence** (task + wishlist stores, SQLite/JSON) lives in `src/modules/task-engine/`.
5
+ * The **`planning` module** (`src/modules/planning/`) is the CLI interview surface (`build-plan`, …).
6
+ * This facade keeps non-task-engine modules from importing deep task-engine paths; implementations stay in task-engine.
7
+ */
8
+ export { openPlanningStores } from "../../modules/task-engine/planning-open.js";
9
+ export { TaskStore } from "../../modules/task-engine/store.js";
10
+ export { WishlistStore } from "../../modules/task-engine/wishlist-store.js";
11
+ export { TransitionService } from "../../modules/task-engine/service.js";
12
+ export { validateKnownTaskTypeRequirements } from "../../modules/task-engine/task-type-validation.js";
13
+ export { buildWishlistItemFromIntake, validateWishlistContentFields, validateWishlistIntakePayload, validateWishlistUpdatePayload, WISHLIST_ID_RE } from "../../modules/task-engine/wishlist-validation.js";
14
+ export { allocateNextTaskNumericId, taskEntityFromNewIntake } from "../../modules/task-engine/wishlist-intake.js";
15
+ export type { TaskEntity, TaskPriority, TaskStatus, TransitionEvidence, TransitionGuard, TransitionContext, GuardResult } from "../../modules/task-engine/types.js";
16
+ export type { WishlistItem, WishlistStatus, WishlistConversionDecomposition } from "../../modules/task-engine/wishlist-types.js";
17
+ export { persistBuildPlanSession, clearBuildPlanSession, readBuildPlanSession, toDashboardPlanningSession, type BuildPlanSessionSnapshotV1, type DashboardPlanningSessionV1 } from "./build-plan-session-file.js";
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Shared planning-domain exports for cross-module consumers.
3
+ *
4
+ * **Planning persistence** (task + wishlist stores, SQLite/JSON) lives in `src/modules/task-engine/`.
5
+ * The **`planning` module** (`src/modules/planning/`) is the CLI interview surface (`build-plan`, …).
6
+ * This facade keeps non-task-engine modules from importing deep task-engine paths; implementations stay in task-engine.
7
+ */
8
+ export { openPlanningStores } from "../../modules/task-engine/planning-open.js";
9
+ export { TaskStore } from "../../modules/task-engine/store.js";
10
+ export { WishlistStore } from "../../modules/task-engine/wishlist-store.js";
11
+ export { TransitionService } from "../../modules/task-engine/service.js";
12
+ export { validateKnownTaskTypeRequirements } from "../../modules/task-engine/task-type-validation.js";
13
+ export { buildWishlistItemFromIntake, validateWishlistContentFields, validateWishlistIntakePayload, validateWishlistUpdatePayload, WISHLIST_ID_RE } from "../../modules/task-engine/wishlist-validation.js";
14
+ export { allocateNextTaskNumericId, taskEntityFromNewIntake } from "../../modules/task-engine/wishlist-intake.js";
15
+ export { persistBuildPlanSession, clearBuildPlanSession, readBuildPlanSession, toDashboardPlanningSession } from "./build-plan-session-file.js";
@@ -1,19 +1,29 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { execFile } from "node:child_process";
4
+ import { APPROVALS_POLICY_COMMAND_NAMES } from "../modules/approvals/policy-sensitive-commands.js";
5
+ import { DOCUMENTATION_POLICY_COMMAND_NAMES } from "../modules/documentation/policy-sensitive-commands.js";
6
+ import { IMPROVEMENT_POLICY_COMMAND_NAMES } from "../modules/improvement/policy-sensitive-commands.js";
7
+ import { TASK_ENGINE_POLICY_COMMAND_NAMES } from "../modules/task-engine/policy-sensitive-commands.js";
4
8
  export const POLICY_TRACE_SCHEMA_VERSION = 1;
5
9
  /** Maintainer doc (repo-relative) linked from policy denial output for `workspace-kit run`. */
6
10
  export const POLICY_APPROVAL_HUMAN_DOC = "docs/maintainers/POLICY-APPROVAL.md";
7
11
  /** Maintainer doc: tier table + copy-paste patterns for agents (Tier A/B `run` vs CLI env approval). */
8
12
  export const AGENT_CLI_MAP_HUMAN_DOC = "docs/maintainers/AGENT-CLI-MAP.md";
9
- const COMMAND_TO_OPERATION = {
10
- "document-project": "doc.document-project",
11
- "generate-document": "doc.generate-document",
12
- "run-transition": "tasks.run-transition",
13
- "review-item": "approvals.review-item",
14
- "generate-recommendations": "improvement.generate-recommendations",
15
- "ingest-transcripts": "improvement.ingest-transcripts"
16
- };
13
+ function buildBuiltinCommandToOperation() {
14
+ const pairs = [
15
+ ...DOCUMENTATION_POLICY_COMMAND_NAMES,
16
+ ...TASK_ENGINE_POLICY_COMMAND_NAMES,
17
+ ...APPROVALS_POLICY_COMMAND_NAMES,
18
+ ...IMPROVEMENT_POLICY_COMMAND_NAMES
19
+ ];
20
+ const out = {};
21
+ for (const [name, op] of pairs) {
22
+ out[name] = op;
23
+ }
24
+ return out;
25
+ }
26
+ const COMMAND_TO_OPERATION = buildBuiltinCommandToOperation();
17
27
  export function getOperationIdForCommand(commandName) {
18
28
  return COMMAND_TO_OPERATION[commandName];
19
29
  }
@@ -0,0 +1,21 @@
1
+ export type ModuleStateRow = {
2
+ moduleId: string;
3
+ stateSchemaVersion: number;
4
+ state: Record<string, unknown>;
5
+ updatedAt: string;
6
+ };
7
+ type UnifiedStateDbOptions = {
8
+ exportSnapshotRelativePath?: string;
9
+ };
10
+ export declare class UnifiedStateDb {
11
+ private db;
12
+ readonly dbPath: string;
13
+ readonly exportSnapshotPath: string | null;
14
+ constructor(workspacePath: string, databaseRelativePath: string, options?: UnifiedStateDbOptions);
15
+ private ensureDb;
16
+ getModuleState(moduleId: string): ModuleStateRow | null;
17
+ setModuleState(moduleId: string, stateSchemaVersion: number, state: Record<string, unknown>): void;
18
+ listModuleStates(): ModuleStateRow[];
19
+ private maybeExportSnapshot;
20
+ }
21
+ export {};