@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
@@ -0,0 +1,6 @@
1
+ import type { ModuleLifecycleContext } from "../../contracts/module-contract.js";
2
+ import type { BehaviorWorkspaceStateV1 } from "./types.js";
3
+ export declare function behaviorPersistenceMode(effectiveConfig: Record<string, unknown> | undefined): "json" | "sqlite";
4
+ export declare function behaviorSqliteRelativePath(effectiveConfig: Record<string, unknown> | undefined): string;
5
+ export declare function loadBehaviorWorkspaceState(ctx: ModuleLifecycleContext): Promise<BehaviorWorkspaceStateV1>;
6
+ export declare function saveBehaviorWorkspaceState(ctx: ModuleLifecycleContext, state: BehaviorWorkspaceStateV1): Promise<void>;
@@ -0,0 +1,89 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { UnifiedStateDb } from "../../core/state/unified-state-db.js";
4
+ const MODULE_ID = "agent-behavior";
5
+ const STATE_SCHEMA = 1;
6
+ const JSON_REL = path.join(".workspace-kit", "agent-behavior", "state.json");
7
+ export function behaviorPersistenceMode(effectiveConfig) {
8
+ const tasks = effectiveConfig?.tasks;
9
+ if (!tasks || typeof tasks !== "object" || Array.isArray(tasks)) {
10
+ return "json";
11
+ }
12
+ const b = tasks.persistenceBackend;
13
+ return b === "sqlite" ? "sqlite" : "json";
14
+ }
15
+ export function behaviorSqliteRelativePath(effectiveConfig) {
16
+ const tasks = effectiveConfig?.tasks;
17
+ if (!tasks || typeof tasks !== "object" || Array.isArray(tasks)) {
18
+ return ".workspace-kit/tasks/workspace-kit.db";
19
+ }
20
+ const p = tasks.sqliteDatabaseRelativePath;
21
+ return typeof p === "string" && p.trim().length > 0
22
+ ? p.trim()
23
+ : ".workspace-kit/tasks/workspace-kit.db";
24
+ }
25
+ function jsonPath(workspacePath) {
26
+ return path.join(workspacePath, JSON_REL);
27
+ }
28
+ function emptyState() {
29
+ return { schemaVersion: 1, activeProfileId: null, customProfiles: {} };
30
+ }
31
+ function parseState(raw) {
32
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
33
+ return emptyState();
34
+ }
35
+ const o = raw;
36
+ if (o.schemaVersion !== 1) {
37
+ return emptyState();
38
+ }
39
+ const custom = o.customProfiles;
40
+ const customProfiles = {};
41
+ if (custom && typeof custom === "object" && !Array.isArray(custom)) {
42
+ for (const [k, v] of Object.entries(custom)) {
43
+ if (typeof k === "string" && k.startsWith("custom:")) {
44
+ customProfiles[k] = v;
45
+ }
46
+ }
47
+ }
48
+ const active = typeof o.activeProfileId === "string" && o.activeProfileId.length > 0
49
+ ? o.activeProfileId
50
+ : null;
51
+ return { schemaVersion: 1, activeProfileId: active, customProfiles };
52
+ }
53
+ export async function loadBehaviorWorkspaceState(ctx) {
54
+ const cfg = ctx.effectiveConfig;
55
+ if (behaviorPersistenceMode(cfg) === "sqlite") {
56
+ const rel = behaviorSqliteRelativePath(cfg);
57
+ const db = new UnifiedStateDb(ctx.workspacePath, rel);
58
+ const row = db.getModuleState(MODULE_ID);
59
+ if (!row?.state) {
60
+ return emptyState();
61
+ }
62
+ return parseState(row.state);
63
+ }
64
+ const fp = jsonPath(ctx.workspacePath);
65
+ try {
66
+ const raw = JSON.parse(await fs.promises.readFile(fp, "utf8"));
67
+ return parseState(raw);
68
+ }
69
+ catch {
70
+ return emptyState();
71
+ }
72
+ }
73
+ export async function saveBehaviorWorkspaceState(ctx, state) {
74
+ const cfg = ctx.effectiveConfig;
75
+ const body = {
76
+ schemaVersion: 1,
77
+ activeProfileId: state.activeProfileId,
78
+ customProfiles: { ...state.customProfiles }
79
+ };
80
+ if (behaviorPersistenceMode(cfg) === "sqlite") {
81
+ const rel = behaviorSqliteRelativePath(cfg);
82
+ const db = new UnifiedStateDb(ctx.workspacePath, rel);
83
+ db.setModuleState(MODULE_ID, STATE_SCHEMA, body);
84
+ return;
85
+ }
86
+ const fp = jsonPath(ctx.workspacePath);
87
+ await fs.promises.mkdir(path.dirname(fp), { recursive: true });
88
+ await fs.promises.writeFile(fp, `${JSON.stringify(body, null, 2)}\n`, "utf8");
89
+ }
@@ -0,0 +1,34 @@
1
+ import type { BehaviorProfile, BehaviorWorkspaceStateV1, BehaviorProvenanceEntry } from "./types.js";
2
+ export declare class BehaviorProfileStore {
3
+ private state;
4
+ constructor(initial: BehaviorWorkspaceStateV1);
5
+ getState(): BehaviorWorkspaceStateV1;
6
+ listIds(): {
7
+ id: string;
8
+ label: string;
9
+ kind: "builtin" | "custom";
10
+ }[];
11
+ getRawProfile(id: string): BehaviorProfile | null;
12
+ /** Resolve `extends` chain; returns null if missing or cycle (cycle truncated). */
13
+ resolveProfile(id: string, stack?: Set<string>): BehaviorProfile | null;
14
+ getActiveProfileId(): string | null;
15
+ setActiveProfileId(id: string | null): void;
16
+ putCustomProfile(profile: BehaviorProfile): void;
17
+ deleteCustomProfile(id: string): void;
18
+ resolveEffectiveWithProvenance(): {
19
+ effective: BehaviorProfile;
20
+ provenance: BehaviorProvenanceEntry[];
21
+ };
22
+ }
23
+ export declare function materializeCustomFromBase(baseId: string, store: BehaviorProfileStore, newId: string, overrides: {
24
+ label?: string;
25
+ summary?: string;
26
+ dimensions?: Partial<BehaviorProfile["dimensions"]>;
27
+ interactionNotes?: string;
28
+ }): {
29
+ ok: true;
30
+ profile: BehaviorProfile;
31
+ } | {
32
+ ok: false;
33
+ message: string;
34
+ };
@@ -0,0 +1,119 @@
1
+ import { BUILTIN_PROFILES, DEFAULT_BUILTIN_PROFILE_ID } from "./builtins.js";
2
+ import { mergeDimensions, validateBehaviorProfile } from "./validate.js";
3
+ export class BehaviorProfileStore {
4
+ state;
5
+ constructor(initial) {
6
+ this.state = initial;
7
+ }
8
+ getState() {
9
+ return this.state;
10
+ }
11
+ listIds() {
12
+ const out = [];
13
+ for (const [id, p] of Object.entries(BUILTIN_PROFILES)) {
14
+ out.push({ id, label: p.label, kind: "builtin" });
15
+ }
16
+ for (const [id, p] of Object.entries(this.state.customProfiles)) {
17
+ out.push({ id, label: p.label, kind: "custom" });
18
+ }
19
+ return out.sort((a, b) => a.id.localeCompare(b.id));
20
+ }
21
+ getRawProfile(id) {
22
+ if (BUILTIN_PROFILES[id]) {
23
+ return { ...BUILTIN_PROFILES[id], dimensions: { ...BUILTIN_PROFILES[id].dimensions } };
24
+ }
25
+ const c = this.state.customProfiles[id];
26
+ if (!c)
27
+ return null;
28
+ return {
29
+ ...c,
30
+ dimensions: { ...c.dimensions },
31
+ metadata: c.metadata ? { ...c.metadata } : undefined
32
+ };
33
+ }
34
+ /** Resolve `extends` chain; returns null if missing or cycle (cycle truncated). */
35
+ resolveProfile(id, stack = new Set()) {
36
+ if (stack.has(id)) {
37
+ return null;
38
+ }
39
+ stack.add(id);
40
+ const raw = this.getRawProfile(id);
41
+ if (!raw) {
42
+ stack.delete(id);
43
+ return null;
44
+ }
45
+ if (!raw.extends) {
46
+ stack.delete(id);
47
+ return raw;
48
+ }
49
+ const base = this.resolveProfile(raw.extends, stack);
50
+ stack.delete(id);
51
+ if (!base) {
52
+ return raw;
53
+ }
54
+ return {
55
+ ...base,
56
+ ...raw,
57
+ id: raw.id,
58
+ label: raw.label,
59
+ summary: raw.summary,
60
+ dimensions: mergeDimensions(base.dimensions, raw.dimensions),
61
+ interactionNotes: raw.interactionNotes ?? base.interactionNotes,
62
+ extends: raw.extends,
63
+ metadata: raw.metadata ?? base.metadata
64
+ };
65
+ }
66
+ getActiveProfileId() {
67
+ return this.state.activeProfileId;
68
+ }
69
+ setActiveProfileId(id) {
70
+ this.state.activeProfileId = id;
71
+ }
72
+ putCustomProfile(profile) {
73
+ this.state.customProfiles[profile.id] = profile;
74
+ }
75
+ deleteCustomProfile(id) {
76
+ delete this.state.customProfiles[id];
77
+ }
78
+ resolveEffectiveWithProvenance() {
79
+ const provenance = [
80
+ { source: "default", profileId: DEFAULT_BUILTIN_PROFILE_ID }
81
+ ];
82
+ let chosen = DEFAULT_BUILTIN_PROFILE_ID;
83
+ const active = this.state.activeProfileId;
84
+ if (active) {
85
+ const resolved = this.resolveProfile(active);
86
+ if (resolved) {
87
+ chosen = active;
88
+ provenance.push({ source: "active", profileId: active });
89
+ return { effective: resolved, provenance };
90
+ }
91
+ provenance.push({ source: "fallback", profileId: DEFAULT_BUILTIN_PROFILE_ID });
92
+ }
93
+ const fallback = this.resolveProfile(DEFAULT_BUILTIN_PROFILE_ID);
94
+ return {
95
+ effective: fallback ?? BUILTIN_PROFILES[DEFAULT_BUILTIN_PROFILE_ID],
96
+ provenance
97
+ };
98
+ }
99
+ }
100
+ export function materializeCustomFromBase(baseId, store, newId, overrides) {
101
+ const baseResolved = store.resolveProfile(baseId);
102
+ if (!baseResolved) {
103
+ return { ok: false, message: `Base profile '${baseId}' not found` };
104
+ }
105
+ const label = overrides.label?.trim() || `${baseResolved.label} (custom)`;
106
+ const summary = overrides.summary?.trim() || baseResolved.summary;
107
+ const dimensions = mergeDimensions(baseResolved.dimensions, overrides.dimensions);
108
+ const draft = {
109
+ schemaVersion: 1,
110
+ id: newId,
111
+ extends: baseId,
112
+ label,
113
+ summary,
114
+ dimensions,
115
+ interactionNotes: overrides.interactionNotes?.trim(),
116
+ metadata: { source: "fork", createdAt: new Date().toISOString(), baseProfileId: baseId }
117
+ };
118
+ return validateBehaviorProfile(draft);
119
+ }
@@ -0,0 +1,28 @@
1
+ export declare const BEHAVIOR_PROFILE_SCHEMA_VERSION: 1;
2
+ export type BehaviorDimensions = {
3
+ deliberationDepth: "low" | "medium" | "high";
4
+ changeAppetite: "conservative" | "balanced" | "bold";
5
+ checkInFrequency: "rare" | "normal" | "often";
6
+ explanationVerbosity: "terse" | "normal" | "verbose";
7
+ explorationStyle: "linear" | "parallel";
8
+ ambiguityHandling: "decide" | "ask";
9
+ };
10
+ export type BehaviorProfile = {
11
+ schemaVersion: typeof BEHAVIOR_PROFILE_SCHEMA_VERSION;
12
+ id: string;
13
+ extends?: string;
14
+ label: string;
15
+ summary: string;
16
+ dimensions: BehaviorDimensions;
17
+ interactionNotes?: string;
18
+ metadata?: Record<string, unknown>;
19
+ };
20
+ export type BehaviorWorkspaceStateV1 = {
21
+ schemaVersion: 1;
22
+ activeProfileId: string | null;
23
+ customProfiles: Record<string, BehaviorProfile>;
24
+ };
25
+ export type BehaviorProvenanceEntry = {
26
+ source: "default" | "active" | "fallback";
27
+ profileId: string;
28
+ };
@@ -0,0 +1 @@
1
+ export const BEHAVIOR_PROFILE_SCHEMA_VERSION = 1;
@@ -0,0 +1,11 @@
1
+ import { type BehaviorDimensions, type BehaviorProfile } from "./types.js";
2
+ export declare function validateBehaviorProfile(raw: unknown, options?: {
3
+ allowBuiltinId?: boolean;
4
+ }): {
5
+ ok: true;
6
+ profile: BehaviorProfile;
7
+ } | {
8
+ ok: false;
9
+ message: string;
10
+ };
11
+ export declare function mergeDimensions(base: BehaviorDimensions, patch: Partial<BehaviorDimensions> | undefined): BehaviorDimensions;
@@ -0,0 +1,123 @@
1
+ import { BEHAVIOR_PROFILE_SCHEMA_VERSION } from "./types.js";
2
+ const ID_RE = /^(builtin|custom):[a-z0-9-]+$/;
3
+ const FORBIDDEN_NOTE_SNIPPETS = [
4
+ "ignore policy",
5
+ "bypass policy",
6
+ "skip approval",
7
+ "no approval",
8
+ "without approval",
9
+ "chat-only approval",
10
+ "override principles",
11
+ "skip tests",
12
+ "delete production",
13
+ "exfiltrate"
14
+ ];
15
+ const DIMENSION_KEYS = [
16
+ "deliberationDepth",
17
+ "changeAppetite",
18
+ "checkInFrequency",
19
+ "explanationVerbosity",
20
+ "explorationStyle",
21
+ "ambiguityHandling"
22
+ ];
23
+ const ENUMS = {
24
+ deliberationDepth: new Set(["low", "medium", "high"]),
25
+ changeAppetite: new Set(["conservative", "balanced", "bold"]),
26
+ checkInFrequency: new Set(["rare", "normal", "often"]),
27
+ explanationVerbosity: new Set(["terse", "normal", "verbose"]),
28
+ explorationStyle: new Set(["linear", "parallel"]),
29
+ ambiguityHandling: new Set(["decide", "ask"])
30
+ };
31
+ export function validateBehaviorProfile(raw, options = {}) {
32
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
33
+ return { ok: false, message: "Profile must be a non-array object" };
34
+ }
35
+ const o = raw;
36
+ if (o.schemaVersion !== BEHAVIOR_PROFILE_SCHEMA_VERSION) {
37
+ return { ok: false, message: `schemaVersion must be ${BEHAVIOR_PROFILE_SCHEMA_VERSION}` };
38
+ }
39
+ const id = typeof o.id === "string" ? o.id.trim() : "";
40
+ if (!ID_RE.test(id)) {
41
+ return { ok: false, message: "id must match builtin:<slug> or custom:<slug> (slug: a-z0-9-)" };
42
+ }
43
+ if (id.startsWith("builtin:") && !options.allowBuiltinId) {
44
+ return { ok: false, message: "Cannot use builtin id for custom profile persistence" };
45
+ }
46
+ if (!id.startsWith("custom:") && !id.startsWith("builtin:")) {
47
+ return { ok: false, message: "Invalid id namespace" };
48
+ }
49
+ const label = typeof o.label === "string" ? o.label.trim() : "";
50
+ const summary = typeof o.summary === "string" ? o.summary.trim() : "";
51
+ if (!label || label.length > 120) {
52
+ return { ok: false, message: "label required, max 120 chars" };
53
+ }
54
+ if (!summary || summary.length > 500) {
55
+ return { ok: false, message: "summary required, max 500 chars" };
56
+ }
57
+ const dimsIn = o.dimensions;
58
+ if (!dimsIn || typeof dimsIn !== "object" || Array.isArray(dimsIn)) {
59
+ return { ok: false, message: "dimensions object required" };
60
+ }
61
+ const drec = dimsIn;
62
+ const dimensions = {};
63
+ for (const key of DIMENSION_KEYS) {
64
+ const v = drec[key];
65
+ if (typeof v !== "string" || !ENUMS[key].has(v)) {
66
+ return { ok: false, message: `dimensions.${key} has invalid value` };
67
+ }
68
+ dimensions[key] = v;
69
+ }
70
+ let interactionNotes;
71
+ if (o.interactionNotes !== undefined) {
72
+ if (typeof o.interactionNotes !== "string") {
73
+ return { ok: false, message: "interactionNotes must be a string" };
74
+ }
75
+ interactionNotes = o.interactionNotes.trim();
76
+ if (interactionNotes.length > 2000) {
77
+ return { ok: false, message: "interactionNotes max 2000 chars" };
78
+ }
79
+ const lower = interactionNotes.toLowerCase();
80
+ for (const bad of FORBIDDEN_NOTE_SNIPPETS) {
81
+ if (lower.includes(bad)) {
82
+ return {
83
+ ok: false,
84
+ message: `interactionNotes must not suggest policy or approval bypass (matched: ${bad})`
85
+ };
86
+ }
87
+ }
88
+ }
89
+ const profile = {
90
+ schemaVersion: BEHAVIOR_PROFILE_SCHEMA_VERSION,
91
+ id,
92
+ label,
93
+ summary,
94
+ dimensions: dimensions,
95
+ interactionNotes,
96
+ metadata: typeof o.metadata === "object" && o.metadata !== null && !Array.isArray(o.metadata)
97
+ ? o.metadata
98
+ : undefined
99
+ };
100
+ if (typeof o.extends === "string" && o.extends.trim().length > 0) {
101
+ const ex = o.extends.trim();
102
+ if (!ID_RE.test(ex)) {
103
+ return { ok: false, message: "extends must be a valid profile id" };
104
+ }
105
+ profile.extends = ex;
106
+ }
107
+ return { ok: true, profile };
108
+ }
109
+ export function mergeDimensions(base, patch) {
110
+ if (!patch)
111
+ return { ...base };
112
+ const out = { ...base };
113
+ for (const key of DIMENSION_KEYS) {
114
+ if (patch[key] !== undefined) {
115
+ const v = patch[key];
116
+ if (!ENUMS[key].has(v)) {
117
+ throw new Error(`Invalid dimension ${key}`);
118
+ }
119
+ out[key] = v;
120
+ }
121
+ }
122
+ return out;
123
+ }
@@ -5,19 +5,16 @@ export const approvalsModule = {
5
5
  id: "approvals",
6
6
  version: "0.5.0",
7
7
  contractVersion: "1",
8
+ stateSchema: 1,
8
9
  capabilities: ["approvals"],
9
10
  dependsOn: ["task-engine"],
11
+ optionalPeers: [],
10
12
  enabledByDefault: true,
11
13
  config: {
12
14
  path: "src/modules/approvals/config.md",
13
15
  format: "md",
14
16
  description: "Approvals module policy and queue configuration contract."
15
17
  },
16
- state: {
17
- path: "src/modules/approvals/state.md",
18
- format: "md",
19
- description: "Approvals module decision and queue state contract."
20
- },
21
18
  instructions: {
22
19
  directory: "src/modules/approvals/instructions",
23
20
  entries: [
@@ -30,57 +27,63 @@ export const approvalsModule = {
30
27
  }
31
28
  },
32
29
  async onCommand(command, ctx) {
33
- if (command.name !== "review-item") {
30
+ const handlers = {
31
+ "review-item": async () => {
32
+ const args = command.args ?? {};
33
+ const actor = typeof args.actor === "string" && args.actor.trim().length > 0
34
+ ? args.actor.trim()
35
+ : ctx.resolvedActor ?? (await resolveActorWithFallback(ctx.workspacePath, args, process.env));
36
+ const taskId = typeof args.taskId === "string" ? args.taskId : "";
37
+ const decision = args.decision;
38
+ if (decision !== "accept" && decision !== "decline" && decision !== "accept_edited") {
39
+ return {
40
+ ok: false,
41
+ code: "invalid-args",
42
+ message: "decision must be accept, decline, or accept_edited"
43
+ };
44
+ }
45
+ const editedSummary = typeof args.editedSummary === "string" ? args.editedSummary : undefined;
46
+ let policyTraceRef;
47
+ const ptr = args.policyTraceRef;
48
+ if (ptr && typeof ptr === "object" && !Array.isArray(ptr)) {
49
+ const o = ptr;
50
+ if (typeof o.operationId === "string" && typeof o.timestamp === "string") {
51
+ policyTraceRef = { operationId: o.operationId, timestamp: o.timestamp };
52
+ }
53
+ }
54
+ let configMutationRef;
55
+ const cmr = args.configMutationRef;
56
+ if (cmr && typeof cmr === "object" && !Array.isArray(cmr)) {
57
+ const o = cmr;
58
+ if (typeof o.timestamp === "string" && typeof o.key === "string") {
59
+ configMutationRef = { timestamp: o.timestamp, key: o.key };
60
+ }
61
+ }
62
+ const result = await runReviewItem(ctx, {
63
+ taskId,
64
+ decision,
65
+ editedSummary,
66
+ policyTraceRef,
67
+ configMutationRef
68
+ }, actor);
69
+ return {
70
+ ok: result.ok,
71
+ code: result.code,
72
+ message: result.message,
73
+ data: "idempotent" in result && result.idempotent
74
+ ? { idempotent: true }
75
+ : undefined
76
+ };
77
+ }
78
+ };
79
+ const handler = handlers[command.name];
80
+ if (!handler) {
34
81
  return {
35
82
  ok: false,
36
83
  code: "unsupported-command",
37
84
  message: `Approvals module does not support '${command.name}'`
38
85
  };
39
86
  }
40
- const args = command.args ?? {};
41
- const actor = typeof args.actor === "string" && args.actor.trim().length > 0
42
- ? args.actor.trim()
43
- : ctx.resolvedActor ?? (await resolveActorWithFallback(ctx.workspacePath, args, process.env));
44
- const taskId = typeof args.taskId === "string" ? args.taskId : "";
45
- const decision = args.decision;
46
- if (decision !== "accept" && decision !== "decline" && decision !== "accept_edited") {
47
- return {
48
- ok: false,
49
- code: "invalid-args",
50
- message: "decision must be accept, decline, or accept_edited"
51
- };
52
- }
53
- const editedSummary = typeof args.editedSummary === "string" ? args.editedSummary : undefined;
54
- let policyTraceRef;
55
- const ptr = args.policyTraceRef;
56
- if (ptr && typeof ptr === "object" && !Array.isArray(ptr)) {
57
- const o = ptr;
58
- if (typeof o.operationId === "string" && typeof o.timestamp === "string") {
59
- policyTraceRef = { operationId: o.operationId, timestamp: o.timestamp };
60
- }
61
- }
62
- let configMutationRef;
63
- const cmr = args.configMutationRef;
64
- if (cmr && typeof cmr === "object" && !Array.isArray(cmr)) {
65
- const o = cmr;
66
- if (typeof o.timestamp === "string" && typeof o.key === "string") {
67
- configMutationRef = { timestamp: o.timestamp, key: o.key };
68
- }
69
- }
70
- const result = await runReviewItem(ctx, {
71
- taskId,
72
- decision,
73
- editedSummary,
74
- policyTraceRef,
75
- configMutationRef
76
- }, actor);
77
- return {
78
- ok: result.ok,
79
- code: result.code,
80
- message: result.message,
81
- data: "idempotent" in result && result.idempotent
82
- ? { idempotent: true }
83
- : undefined
84
- };
87
+ return handler();
85
88
  }
86
89
  };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Policy-gated `workspace-kit run` commands owned by the approvals module.
3
+ */
4
+ export declare const APPROVALS_POLICY_COMMAND_NAMES: readonly [readonly ["review-item", "approvals.review-item"]];
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Policy-gated `workspace-kit run` commands owned by the approvals module.
3
+ */
4
+ export const APPROVALS_POLICY_COMMAND_NAMES = [["review-item", "approvals.review-item"]];
@@ -1,6 +1,5 @@
1
1
  import { appendLineageEvent } from "../../core/lineage-store.js";
2
- import { openPlanningStores } from "../task-engine/planning-open.js";
3
- import { TransitionService } from "../task-engine/service.js";
2
+ import { openPlanningStores, TransitionService } from "../../core/planning/index.js";
4
3
  import { appendDecisionRecord, computeDecisionFingerprint, readDecisionFingerprints } from "./decisions-store.js";
5
4
  function getEvidenceKey(task) {
6
5
  const m = task.metadata;