@workflow-cannon/workspace-kit 0.18.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 (140) 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 +61 -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 +8 -0
  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/index.js +384 -50
  81. package/dist/modules/planning/question-engine.d.ts +2 -0
  82. package/dist/modules/planning/question-engine.js +8 -1
  83. package/dist/modules/task-engine/doctor-planning-persistence.js +21 -13
  84. package/dist/modules/task-engine/index.d.ts +1 -2
  85. package/dist/modules/task-engine/index.js +1 -1143
  86. package/dist/modules/task-engine/migrate-task-persistence-runtime.js +31 -4
  87. package/dist/modules/task-engine/migrate-wishlist-intake-runtime.d.ts +2 -0
  88. package/dist/modules/task-engine/migrate-wishlist-intake-runtime.js +146 -0
  89. package/dist/modules/task-engine/planning-open.d.ts +2 -9
  90. package/dist/modules/task-engine/planning-open.js +4 -15
  91. package/dist/modules/task-engine/policy-sensitive-commands.d.ts +5 -0
  92. package/dist/modules/task-engine/policy-sensitive-commands.js +5 -0
  93. package/dist/modules/task-engine/sqlite-dual-planning.d.ts +11 -2
  94. package/dist/modules/task-engine/sqlite-dual-planning.js +134 -28
  95. package/dist/modules/task-engine/strict-task-validation.js +3 -0
  96. package/dist/modules/task-engine/suggestions.js +2 -1
  97. package/dist/modules/task-engine/task-engine-internal.d.ts +2 -0
  98. package/dist/modules/task-engine/task-engine-internal.js +1304 -0
  99. package/dist/modules/task-engine/task-type-validation.js +40 -0
  100. package/dist/modules/task-engine/wishlist-intake.d.ts +22 -0
  101. package/dist/modules/task-engine/wishlist-intake.js +180 -0
  102. package/dist/modules/task-engine/wishlist-validation.d.ts +4 -0
  103. package/dist/modules/task-engine/wishlist-validation.js +19 -0
  104. package/dist/modules/workspace-config/index.js +9 -11
  105. package/package.json +2 -2
  106. package/schemas/agent-behavior-profile.schema.json +52 -0
  107. package/schemas/task-engine-run-contracts.schema.json +80 -5
  108. package/src/modules/documentation/README.md +16 -25
  109. package/src/modules/documentation/RULES.md +9 -9
  110. package/src/modules/documentation/index.ts +54 -49
  111. package/src/modules/documentation/instructions/document-project.md +6 -6
  112. package/src/modules/documentation/instructions/generate-document.md +4 -4
  113. package/src/modules/documentation/normalizer.ts +187 -0
  114. package/src/modules/documentation/parser.ts +41 -0
  115. package/src/modules/documentation/policy-sensitive-commands.ts +8 -0
  116. package/src/modules/documentation/renderer.ts +121 -0
  117. package/src/modules/documentation/runtime-batch.ts +74 -0
  118. package/src/modules/documentation/runtime-config.ts +68 -0
  119. package/src/modules/documentation/runtime-render-support.ts +39 -0
  120. package/src/modules/documentation/runtime.ts +28 -600
  121. package/src/modules/documentation/schemas/documentation-schema.md +37 -54
  122. package/src/modules/documentation/types.ts +228 -0
  123. package/src/modules/documentation/validator.ts +247 -0
  124. package/src/modules/documentation/view-models.ts +132 -0
  125. package/src/modules/documentation/views/agents.view.yaml +18 -0
  126. package/src/modules/documentation/views/architecture.view.yaml +18 -0
  127. package/src/modules/documentation/views/principles.view.yaml +18 -0
  128. package/src/modules/documentation/views/readme.view.yaml +18 -0
  129. package/src/modules/documentation/views/releasing.view.yaml +18 -0
  130. package/src/modules/documentation/views/roadmap.view.yaml +18 -0
  131. package/src/modules/documentation/views/runbooks-consumer-cadence.view.yaml +18 -0
  132. package/src/modules/documentation/views/runbooks-parity-validation-flow.view.yaml +18 -0
  133. package/src/modules/documentation/views/runbooks-release-channels.view.yaml +18 -0
  134. package/src/modules/documentation/views/security.view.yaml +18 -0
  135. package/src/modules/documentation/views/support.view.yaml +18 -0
  136. package/src/modules/documentation/views/terms.view.yaml +18 -0
  137. package/src/modules/documentation/views/workbooks-phase2-config-policy-workbook.view.yaml +18 -0
  138. package/src/modules/documentation/views/workbooks-task-engine-workbook.view.yaml +18 -0
  139. package/src/modules/documentation/views/workbooks-transcript-automation-baseline.view.yaml +18 -0
  140. package/src/modules/documentation/state.md +0 -8
@@ -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 {};
@@ -0,0 +1,80 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import Database from "better-sqlite3";
4
+ const DDL = `
5
+ CREATE TABLE IF NOT EXISTS workspace_module_state (
6
+ module_id TEXT PRIMARY KEY,
7
+ state_schema_version INTEGER NOT NULL,
8
+ state_json TEXT NOT NULL,
9
+ updated_at TEXT NOT NULL
10
+ );
11
+ `;
12
+ export class UnifiedStateDb {
13
+ db = null;
14
+ dbPath;
15
+ exportSnapshotPath;
16
+ constructor(workspacePath, databaseRelativePath, options) {
17
+ this.dbPath = path.resolve(workspacePath, databaseRelativePath);
18
+ this.exportSnapshotPath = options?.exportSnapshotRelativePath
19
+ ? path.resolve(workspacePath, options.exportSnapshotRelativePath)
20
+ : null;
21
+ }
22
+ ensureDb() {
23
+ if (this.db)
24
+ return this.db;
25
+ fs.mkdirSync(path.dirname(this.dbPath), { recursive: true });
26
+ this.db = new Database(this.dbPath);
27
+ this.db.pragma("journal_mode = WAL");
28
+ this.db.exec(DDL);
29
+ return this.db;
30
+ }
31
+ getModuleState(moduleId) {
32
+ const db = this.ensureDb();
33
+ const row = db
34
+ .prepare("SELECT module_id, state_schema_version, state_json, updated_at FROM workspace_module_state WHERE module_id = ?")
35
+ .get(moduleId);
36
+ if (!row)
37
+ return null;
38
+ return {
39
+ moduleId: row.module_id,
40
+ stateSchemaVersion: row.state_schema_version,
41
+ state: JSON.parse(row.state_json),
42
+ updatedAt: row.updated_at
43
+ };
44
+ }
45
+ setModuleState(moduleId, stateSchemaVersion, state) {
46
+ const db = this.ensureDb();
47
+ const updatedAt = new Date().toISOString();
48
+ db.prepare(`INSERT INTO workspace_module_state (module_id, state_schema_version, state_json, updated_at)
49
+ VALUES (?, ?, ?, ?)
50
+ ON CONFLICT(module_id) DO UPDATE SET
51
+ state_schema_version=excluded.state_schema_version,
52
+ state_json=excluded.state_json,
53
+ updated_at=excluded.updated_at`).run(moduleId, stateSchemaVersion, JSON.stringify(state), updatedAt);
54
+ this.maybeExportSnapshot();
55
+ }
56
+ listModuleStates() {
57
+ const db = this.ensureDb();
58
+ const rows = db
59
+ .prepare("SELECT module_id, state_schema_version, state_json, updated_at FROM workspace_module_state ORDER BY module_id ASC")
60
+ .all();
61
+ return rows.map((row) => ({
62
+ moduleId: row.module_id,
63
+ stateSchemaVersion: row.state_schema_version,
64
+ state: JSON.parse(row.state_json),
65
+ updatedAt: row.updated_at
66
+ }));
67
+ }
68
+ maybeExportSnapshot() {
69
+ if (!this.exportSnapshotPath)
70
+ return;
71
+ const snapshot = {
72
+ schemaVersion: 1,
73
+ exportedAt: new Date().toISOString(),
74
+ dbPath: this.dbPath,
75
+ modules: this.listModuleStates()
76
+ };
77
+ fs.mkdirSync(path.dirname(this.exportSnapshotPath), { recursive: true });
78
+ fs.writeFileSync(this.exportSnapshotPath, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
79
+ }
80
+ }
@@ -10,6 +10,14 @@ export function getProjectConfigPath(workspacePath) {
10
10
  /** Built-in defaults (lowest layer). */
11
11
  export const KIT_CONFIG_DEFAULTS = {
12
12
  core: {},
13
+ /**
14
+ * Module enablement: `enabled` whitelist (non-empty replaces default-by-flag set), then `disabled` subtracts.
15
+ * Empty arrays = no effect (all modules use registration.enabledByDefault).
16
+ */
17
+ modules: {
18
+ enabled: [],
19
+ disabled: []
20
+ },
13
21
  tasks: {
14
22
  storeRelativePath: ".workspace-kit/tasks/state.json",
15
23
  strictValidation: false
@@ -0,0 +1,3 @@
1
+ import { type BehaviorProfile } from "./types.js";
2
+ export declare const DEFAULT_BUILTIN_PROFILE_ID = "builtin:balanced";
3
+ export declare const BUILTIN_PROFILES: Record<string, BehaviorProfile>;
@@ -0,0 +1,71 @@
1
+ import { BEHAVIOR_PROFILE_SCHEMA_VERSION } from "./types.js";
2
+ export const DEFAULT_BUILTIN_PROFILE_ID = "builtin:balanced";
3
+ const nowMeta = () => ({
4
+ source: "builtin",
5
+ createdAt: new Date().toISOString()
6
+ });
7
+ export const BUILTIN_PROFILES = {
8
+ "builtin:cautious": {
9
+ schemaVersion: BEHAVIOR_PROFILE_SCHEMA_VERSION,
10
+ id: "builtin:cautious",
11
+ label: "Cautious",
12
+ summary: "Prefer small steps, frequent check-ins, and explicit confirmation before larger edits.",
13
+ dimensions: {
14
+ deliberationDepth: "high",
15
+ changeAppetite: "conservative",
16
+ checkInFrequency: "often",
17
+ explanationVerbosity: "verbose",
18
+ explorationStyle: "linear",
19
+ ambiguityHandling: "ask"
20
+ },
21
+ interactionNotes: "Surface risks early; default to the smallest reversible change.",
22
+ metadata: nowMeta()
23
+ },
24
+ "builtin:balanced": {
25
+ schemaVersion: BEHAVIOR_PROFILE_SCHEMA_VERSION,
26
+ id: "builtin:balanced",
27
+ label: "Balanced",
28
+ summary: "Default collaboration: clear reasoning, normal autonomy when intent is clear.",
29
+ dimensions: {
30
+ deliberationDepth: "medium",
31
+ changeAppetite: "balanced",
32
+ checkInFrequency: "normal",
33
+ explanationVerbosity: "normal",
34
+ explorationStyle: "linear",
35
+ ambiguityHandling: "ask"
36
+ },
37
+ metadata: nowMeta()
38
+ },
39
+ "builtin:calculated": {
40
+ schemaVersion: BEHAVIOR_PROFILE_SCHEMA_VERSION,
41
+ id: "builtin:calculated",
42
+ label: "Calculated",
43
+ summary: "Structured analysis and explicit tradeoffs before acting; still respects governance.",
44
+ dimensions: {
45
+ deliberationDepth: "high",
46
+ changeAppetite: "balanced",
47
+ checkInFrequency: "normal",
48
+ explanationVerbosity: "verbose",
49
+ explorationStyle: "linear",
50
+ ambiguityHandling: "ask"
51
+ },
52
+ interactionNotes: "Lay out options with pros/cons; prefer evidence-backed recommendations.",
53
+ metadata: nowMeta()
54
+ },
55
+ "builtin:experimental": {
56
+ schemaVersion: BEHAVIOR_PROFILE_SCHEMA_VERSION,
57
+ id: "builtin:experimental",
58
+ label: "Experimental",
59
+ summary: "Try alternatives and parallel approaches in low-risk areas; still obey policy gates.",
60
+ dimensions: {
61
+ deliberationDepth: "medium",
62
+ changeAppetite: "bold",
63
+ checkInFrequency: "normal",
64
+ explanationVerbosity: "normal",
65
+ explorationStyle: "parallel",
66
+ ambiguityHandling: "decide"
67
+ },
68
+ interactionNotes: "Experimental does not mean skipping tests, approvals, or PRINCIPLES—only style of exploration.",
69
+ metadata: nowMeta()
70
+ }
71
+ };
@@ -0,0 +1,6 @@
1
+ import type { BehaviorProfile } from "./types.js";
2
+ export declare function summarizeProfileMarkdown(profile: BehaviorProfile): string;
3
+ export declare function diffProfiles(a: BehaviorProfile, b: BehaviorProfile): Record<string, {
4
+ from: string;
5
+ to: string;
6
+ }>;