@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.
- package/README.md +23 -9
- package/dist/cli/doctor-planning-issues.js +3 -22
- package/dist/cli/run-command.js +22 -38
- package/dist/cli.js +95 -4
- package/dist/contracts/command-manifest.d.ts +17 -0
- package/dist/contracts/command-manifest.js +1 -0
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/module-contract.d.ts +12 -11
- package/dist/core/agent-instruction-surface.d.ts +33 -0
- package/dist/core/agent-instruction-surface.js +46 -0
- package/dist/core/config-cli.js +13 -17
- package/dist/core/config-metadata.js +101 -2
- package/dist/core/index.d.ts +4 -1
- package/dist/core/index.js +3 -0
- package/dist/core/module-command-router.js +19 -1
- package/dist/core/module-registry-resolve.d.ts +27 -0
- package/dist/core/module-registry-resolve.js +91 -0
- package/dist/core/module-registry.d.ts +14 -0
- package/dist/core/module-registry.js +57 -0
- package/dist/core/planning/build-plan-session-file.d.ts +29 -0
- package/dist/core/planning/build-plan-session-file.js +58 -0
- package/dist/core/planning/index.d.ts +17 -0
- package/dist/core/planning/index.js +15 -0
- package/dist/core/policy.js +18 -8
- package/dist/core/state/unified-state-db.d.ts +21 -0
- package/dist/core/state/unified-state-db.js +80 -0
- package/dist/core/workspace-kit-config.js +13 -1
- package/dist/modules/agent-behavior/builtins.d.ts +3 -0
- package/dist/modules/agent-behavior/builtins.js +71 -0
- package/dist/modules/agent-behavior/explain.d.ts +6 -0
- package/dist/modules/agent-behavior/explain.js +46 -0
- package/dist/modules/agent-behavior/index.d.ts +4 -0
- package/dist/modules/agent-behavior/index.js +461 -0
- package/dist/modules/agent-behavior/interview-session-file.d.ts +9 -0
- package/dist/modules/agent-behavior/interview-session-file.js +43 -0
- package/dist/modules/agent-behavior/interview.d.ts +13 -0
- package/dist/modules/agent-behavior/interview.js +88 -0
- package/dist/modules/agent-behavior/persistence.d.ts +6 -0
- package/dist/modules/agent-behavior/persistence.js +89 -0
- package/dist/modules/agent-behavior/store.d.ts +34 -0
- package/dist/modules/agent-behavior/store.js +119 -0
- package/dist/modules/agent-behavior/types.d.ts +28 -0
- package/dist/modules/agent-behavior/types.js +1 -0
- package/dist/modules/agent-behavior/validate.d.ts +11 -0
- package/dist/modules/agent-behavior/validate.js +123 -0
- package/dist/modules/approvals/index.js +54 -51
- package/dist/modules/approvals/policy-sensitive-commands.d.ts +4 -0
- package/dist/modules/approvals/policy-sensitive-commands.js +4 -0
- package/dist/modules/approvals/review-runtime.js +1 -2
- package/dist/modules/documentation/index.js +47 -45
- package/dist/modules/documentation/normalizer.d.ts +3 -0
- package/dist/modules/documentation/normalizer.js +171 -0
- package/dist/modules/documentation/parser.d.ts +7 -0
- package/dist/modules/documentation/parser.js +39 -0
- package/dist/modules/documentation/policy-sensitive-commands.d.ts +5 -0
- package/dist/modules/documentation/policy-sensitive-commands.js +8 -0
- package/dist/modules/documentation/renderer.d.ts +23 -0
- package/dist/modules/documentation/renderer.js +105 -0
- package/dist/modules/documentation/runtime-batch.d.ts +10 -0
- package/dist/modules/documentation/runtime-batch.js +67 -0
- package/dist/modules/documentation/runtime-config.d.ts +11 -0
- package/dist/modules/documentation/runtime-config.js +54 -0
- package/dist/modules/documentation/runtime-render-support.d.ts +8 -0
- package/dist/modules/documentation/runtime-render-support.js +36 -0
- package/dist/modules/documentation/runtime.js +22 -510
- package/dist/modules/documentation/types.d.ts +182 -0
- package/dist/modules/documentation/validator.d.ts +8 -0
- package/dist/modules/documentation/validator.js +234 -0
- package/dist/modules/documentation/view-models.d.ts +3 -0
- package/dist/modules/documentation/view-models.js +124 -0
- package/dist/modules/improvement/generate-recommendations-runtime.js +3 -3
- package/dist/modules/improvement/improvement-state.d.ts +2 -2
- package/dist/modules/improvement/improvement-state.js +52 -23
- package/dist/modules/improvement/index.js +140 -138
- package/dist/modules/improvement/ingest.d.ts +1 -1
- package/dist/modules/improvement/policy-sensitive-commands.d.ts +4 -0
- package/dist/modules/improvement/policy-sensitive-commands.js +7 -0
- package/dist/modules/index.d.ts +6 -0
- package/dist/modules/index.js +17 -0
- package/dist/modules/planning/artifact.d.ts +19 -0
- package/dist/modules/planning/artifact.js +72 -0
- package/dist/modules/planning/index.js +605 -6
- package/dist/modules/planning/question-engine.d.ts +25 -0
- package/dist/modules/planning/question-engine.js +284 -0
- package/dist/modules/planning/types.d.ts +9 -0
- package/dist/modules/planning/types.js +39 -0
- package/dist/modules/task-engine/doctor-planning-persistence.js +21 -13
- package/dist/modules/task-engine/index.d.ts +1 -2
- package/dist/modules/task-engine/index.js +1 -1143
- package/dist/modules/task-engine/migrate-task-persistence-runtime.js +31 -4
- package/dist/modules/task-engine/migrate-wishlist-intake-runtime.d.ts +2 -0
- package/dist/modules/task-engine/migrate-wishlist-intake-runtime.js +146 -0
- package/dist/modules/task-engine/planning-open.d.ts +2 -9
- package/dist/modules/task-engine/planning-open.js +4 -15
- package/dist/modules/task-engine/policy-sensitive-commands.d.ts +5 -0
- package/dist/modules/task-engine/policy-sensitive-commands.js +5 -0
- package/dist/modules/task-engine/sqlite-dual-planning.d.ts +11 -2
- package/dist/modules/task-engine/sqlite-dual-planning.js +134 -28
- package/dist/modules/task-engine/strict-task-validation.js +3 -0
- package/dist/modules/task-engine/suggestions.js +2 -1
- package/dist/modules/task-engine/task-engine-internal.d.ts +2 -0
- package/dist/modules/task-engine/task-engine-internal.js +1304 -0
- package/dist/modules/task-engine/task-type-validation.js +40 -0
- package/dist/modules/task-engine/wishlist-intake.d.ts +22 -0
- package/dist/modules/task-engine/wishlist-intake.js +180 -0
- package/dist/modules/task-engine/wishlist-validation.d.ts +4 -0
- package/dist/modules/task-engine/wishlist-validation.js +19 -0
- package/dist/modules/workspace-config/index.js +9 -11
- package/package.json +2 -2
- package/schemas/agent-behavior-profile.schema.json +52 -0
- package/schemas/task-engine-run-contracts.schema.json +80 -5
- package/src/modules/documentation/README.md +16 -25
- package/src/modules/documentation/RULES.md +9 -9
- package/src/modules/documentation/index.ts +54 -49
- package/src/modules/documentation/instructions/document-project.md +6 -6
- package/src/modules/documentation/instructions/generate-document.md +4 -4
- package/src/modules/documentation/normalizer.ts +187 -0
- package/src/modules/documentation/parser.ts +41 -0
- package/src/modules/documentation/policy-sensitive-commands.ts +8 -0
- package/src/modules/documentation/renderer.ts +121 -0
- package/src/modules/documentation/runtime-batch.ts +74 -0
- package/src/modules/documentation/runtime-config.ts +68 -0
- package/src/modules/documentation/runtime-render-support.ts +39 -0
- package/src/modules/documentation/runtime.ts +28 -600
- package/src/modules/documentation/schemas/documentation-schema.md +37 -54
- package/src/modules/documentation/types.ts +228 -0
- package/src/modules/documentation/validator.ts +247 -0
- package/src/modules/documentation/view-models.ts +132 -0
- package/src/modules/documentation/views/agents.view.yaml +18 -0
- package/src/modules/documentation/views/architecture.view.yaml +18 -0
- package/src/modules/documentation/views/principles.view.yaml +18 -0
- package/src/modules/documentation/views/readme.view.yaml +18 -0
- package/src/modules/documentation/views/releasing.view.yaml +18 -0
- package/src/modules/documentation/views/roadmap.view.yaml +18 -0
- package/src/modules/documentation/views/runbooks-consumer-cadence.view.yaml +18 -0
- package/src/modules/documentation/views/runbooks-parity-validation-flow.view.yaml +18 -0
- package/src/modules/documentation/views/runbooks-release-channels.view.yaml +18 -0
- package/src/modules/documentation/views/security.view.yaml +18 -0
- package/src/modules/documentation/views/support.view.yaml +18 -0
- package/src/modules/documentation/views/terms.view.yaml +18 -0
- package/src/modules/documentation/views/workbooks-phase2-config-policy-workbook.view.yaml +18 -0
- package/src/modules/documentation/views/workbooks-task-engine-workbook.view.yaml +18 -0
- package/src/modules/documentation/views/workbooks-transcript-automation-baseline.view.yaml +18 -0
- package/src/modules/documentation/state.md +0 -8
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { appendLineageEvent } from "../../core/lineage-store.js";
|
|
2
|
-
import { openPlanningStores } from "
|
|
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;
|