@voybio/ace-swarm 2.4.4 → 2.4.6

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 (120) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +72 -532
  3. package/assets/eval-battery/battery.json +85 -0
  4. package/assets/eval-battery/fixtures/edit-target.js +3 -0
  5. package/assets/scripts/render-mcp-configs.sh +12 -0
  6. package/dist/ace-context.d.ts +18 -0
  7. package/dist/ace-context.js +161 -16
  8. package/dist/ace-server-instructions.js +36 -9
  9. package/dist/bridge-trace.d.ts +17 -0
  10. package/dist/bridge-trace.js +23 -0
  11. package/dist/cli.d.ts +9 -1
  12. package/dist/cli.js +619 -10
  13. package/dist/discovery-runtime-wrappers.d.ts +16 -1
  14. package/dist/discovery-runtime-wrappers.js +65 -9
  15. package/dist/eval-harness.d.ts +110 -0
  16. package/dist/eval-harness.js +254 -0
  17. package/dist/experience-reflector.d.ts +13 -0
  18. package/dist/experience-reflector.js +62 -0
  19. package/dist/goal-engine.d.ts +155 -0
  20. package/dist/goal-engine.js +795 -0
  21. package/dist/helpers/store-resolution.js +40 -39
  22. package/dist/hermes/session-manager.d.ts +10 -0
  23. package/dist/hermes/session-manager.js +130 -27
  24. package/dist/job-scheduler.d.ts +58 -0
  25. package/dist/job-scheduler.js +202 -2
  26. package/dist/job-worker.d.ts +87 -0
  27. package/dist/job-worker.js +182 -0
  28. package/dist/local-model-policy.d.ts +8 -2
  29. package/dist/local-model-policy.js +31 -17
  30. package/dist/local-model-runtime.d.ts +6 -0
  31. package/dist/local-model-runtime.js +130 -2
  32. package/dist/measured-classing.d.ts +66 -0
  33. package/dist/measured-classing.js +165 -0
  34. package/dist/model-bridge.d.ts +45 -0
  35. package/dist/model-bridge.js +502 -44
  36. package/dist/orchestrator-supervisor.js +1 -1
  37. package/dist/plan-proposal.d.ts +7 -0
  38. package/dist/plan-proposal.js +74 -4
  39. package/dist/playbook-adaptation.d.ts +37 -0
  40. package/dist/playbook-adaptation.js +159 -0
  41. package/dist/playbook-curator.d.ts +16 -0
  42. package/dist/playbook-curator.js +106 -0
  43. package/dist/playbook-llm-reflector.d.ts +26 -0
  44. package/dist/playbook-llm-reflector.js +157 -0
  45. package/dist/public-surface.d.ts +2 -0
  46. package/dist/public-surface.js +26 -14
  47. package/dist/safe-edit.d.ts +20 -0
  48. package/dist/safe-edit.js +355 -78
  49. package/dist/server.d.ts +11 -2
  50. package/dist/server.js +55 -5
  51. package/dist/shared.d.ts +12 -0
  52. package/dist/shared.js +13 -1
  53. package/dist/source-mutation-coordinator.d.ts +54 -0
  54. package/dist/source-mutation-coordinator.js +259 -0
  55. package/dist/status-events.d.ts +4 -1
  56. package/dist/status-events.js +41 -6
  57. package/dist/store/ace-packed-store.d.ts +28 -9
  58. package/dist/store/ace-packed-store.js +324 -57
  59. package/dist/store/bgzf.d.ts +4 -0
  60. package/dist/store/bgzf.js +94 -0
  61. package/dist/store/bootstrap-store.d.ts +4 -1
  62. package/dist/store/bootstrap-store.js +57 -3
  63. package/dist/store/cache-workspace.d.ts +8 -0
  64. package/dist/store/cache-workspace.js +67 -1
  65. package/dist/store/crc32.d.ts +2 -0
  66. package/dist/store/crc32.js +16 -0
  67. package/dist/store/fsck.d.ts +16 -0
  68. package/dist/store/fsck.js +240 -0
  69. package/dist/store/materializers/context-snapshot-materializer.d.ts +1 -0
  70. package/dist/store/materializers/context-snapshot-materializer.js +4 -1
  71. package/dist/store/materializers/projection-manager.d.ts +7 -0
  72. package/dist/store/materializers/projection-manager.js +37 -0
  73. package/dist/store/materializers/scheduler-projection-materializer.js +10 -3
  74. package/dist/store/repositories/goal-repository.d.ts +112 -0
  75. package/dist/store/repositories/goal-repository.js +157 -0
  76. package/dist/store/repositories/playbook-repository.d.ts +107 -0
  77. package/dist/store/repositories/playbook-repository.js +314 -0
  78. package/dist/store/store-artifacts.js +44 -2
  79. package/dist/store/store-authority-audit.js +1 -0
  80. package/dist/store/store-cli.d.ts +30 -0
  81. package/dist/store/store-cli.js +242 -0
  82. package/dist/store/store-snapshot.d.ts +12 -0
  83. package/dist/store/store-snapshot.js +74 -11
  84. package/dist/store/types.d.ts +1 -0
  85. package/dist/store/write-coordinator.d.ts +7 -0
  86. package/dist/store/write-coordinator.js +11 -0
  87. package/dist/todo-state.d.ts +21 -0
  88. package/dist/todo-state.js +150 -53
  89. package/dist/tools-agent.js +86 -22
  90. package/dist/tools-discovery.js +5 -3
  91. package/dist/tools-files.js +39 -18
  92. package/dist/tools-framework.js +31 -3
  93. package/dist/tools-goal.d.ts +11 -0
  94. package/dist/tools-goal.js +108 -0
  95. package/dist/tools-handoff.js +7 -11
  96. package/dist/tools-lifecycle.js +2 -0
  97. package/dist/tools-scheduler.js +44 -1
  98. package/dist/tools-skills.js +3 -2
  99. package/dist/tools-todo.js +64 -3
  100. package/dist/tools.js +2 -0
  101. package/dist/tui/chat.js +4 -0
  102. package/dist/tui/index.js +4 -1
  103. package/dist/tui/ollama.d.ts +2 -0
  104. package/dist/tui/openai-compatible.d.ts +14 -0
  105. package/dist/tui/openai-compatible.js +15 -2
  106. package/dist/tui/provider-discovery.d.ts +1 -0
  107. package/dist/tui/provider-discovery.js +2 -2
  108. package/docs/cli-reference.md +125 -0
  109. package/docs/development.md +46 -0
  110. package/docs/evaluation-safety.md +27 -0
  111. package/docs/experience-playbook.md +35 -0
  112. package/docs/hermes.md +129 -0
  113. package/docs/library-api.md +31 -0
  114. package/docs/mcp-tool-reference.md +290 -0
  115. package/docs/provider-security.md +85 -0
  116. package/docs/state-model.md +113 -0
  117. package/docs/troubleshooting.md +95 -0
  118. package/docs/tui-operator-guide.md +75 -0
  119. package/package.json +3 -1
  120. package/scripts/dev/run_hermes_crispr_live.mjs +212 -0
@@ -0,0 +1,112 @@
1
+ /**
2
+ * GoalRepository — durable storage for the ACE Goal Engine (WS-F).
3
+ *
4
+ * Persists one {@link GoalRecord} plus its active {@link TaskPlan} per goal, and
5
+ * a lightweight index for listing. Storage is plain JSON under
6
+ * `<workspaceRoot>/agent-state/goals/`, written atomically (temp + rename) so a
7
+ * crash mid-write never leaves a torn record — the property WS-F's resume path
8
+ * depends on. The repository is intentionally root-parameterized (rather than
9
+ * bound to the packed store) so the Goal Engine and its tests can drive it
10
+ * hermetically against a temp workspace.
11
+ *
12
+ * Layout:
13
+ * agent-state/goals/index.json { version, updated_at, goals: [...] }
14
+ * agent-state/goals/<goal_id>.json GoalRecord
15
+ * agent-state/goals/<goal_id>.plan.json TaskPlan (rewritten every transition)
16
+ */
17
+ import type { TaskPlan } from "../../orchestrator-supervisor.js";
18
+ export type GoalStatus = "draft" | "planning" | "executing" | "verifying" | "blocked" | "done" | "failed" | "stopped";
19
+ export type GoalEscalationKind = "approval_required" | "run_complete" | "run_error" | "gate_failed" | "budget_blocked" | "blocker";
20
+ export interface GoalBudgets {
21
+ max_wall_clock_ms: number;
22
+ max_ticks: number;
23
+ max_replans: number;
24
+ max_step_retries: number;
25
+ }
26
+ export interface SuccessContract {
27
+ /** Union of step-level and goal-level required artifacts. */
28
+ expected_artifacts: Array<{
29
+ path: string;
30
+ required?: boolean;
31
+ }>;
32
+ required_evidence_refs: string[];
33
+ verification_commands: string[];
34
+ /**
35
+ * TODO(WS-F): wire gate_ids to execute_gates. Always `[]` today and never
36
+ * read, so it is deliberately excluded from `enforceableObligationCount`.
37
+ */
38
+ gate_ids: string[];
39
+ /**
40
+ * Success criteria that could not be compiled to a machine-checkable form.
41
+ * Never silently dropped (L3): the engine routes these to a terminal qa
42
+ * review step so a human/qa role adjudicates them.
43
+ */
44
+ uncompilable_criteria: string[];
45
+ }
46
+ export interface GoalEscalation {
47
+ at: string;
48
+ kind: GoalEscalationKind;
49
+ reason_code: string;
50
+ summary: string;
51
+ }
52
+ export interface GoalRecord {
53
+ goal_id: string;
54
+ objective: string;
55
+ status: GoalStatus;
56
+ plan_id?: string;
57
+ success_contract: SuccessContract;
58
+ budgets: GoalBudgets;
59
+ created_at: string;
60
+ updated_at: string;
61
+ started_at?: string;
62
+ tick_count: number;
63
+ replan_count: number;
64
+ last_heartbeat_at?: string;
65
+ stop_requested: boolean;
66
+ escalations: GoalEscalation[];
67
+ result_summary?: string;
68
+ reason_code?: string;
69
+ current_step_id?: string;
70
+ }
71
+ export interface GoalIndexEntry {
72
+ goal_id: string;
73
+ status: GoalStatus;
74
+ objective: string;
75
+ updated_at: string;
76
+ }
77
+ export interface GoalIndexFile {
78
+ version: 1;
79
+ updated_at: string;
80
+ goals: GoalIndexEntry[];
81
+ }
82
+ export declare function newGoalId(): string;
83
+ export declare class GoalRepository {
84
+ private readonly workspaceRoot;
85
+ private readonly dir;
86
+ constructor(workspaceRoot: string);
87
+ private ensureDir;
88
+ private goalPath;
89
+ private planPath;
90
+ private indexPath;
91
+ private writeAtomic;
92
+ private readJson;
93
+ readIndex(): GoalIndexFile;
94
+ private upsertIndex;
95
+ /** Persist a goal record and refresh the index in one call. */
96
+ saveGoal(goal: GoalRecord): GoalRecord;
97
+ getGoal(goalId: string): GoalRecord | undefined;
98
+ savePlan(goalId: string, plan: TaskPlan): void;
99
+ getPlan(goalId: string): TaskPlan | undefined;
100
+ listGoals(filter?: {
101
+ status?: GoalStatus;
102
+ }): GoalIndexEntry[];
103
+ /** Delete a goal, its plan, and its index entry. Returns true if present. */
104
+ deleteGoal(goalId: string): boolean;
105
+ /**
106
+ * Rebuild the index from the on-disk goal files. Cheap self-heal used at
107
+ * startup so a hand-deleted or partially-written index never hides live goals.
108
+ */
109
+ rebuildIndex(): GoalIndexFile;
110
+ }
111
+ export declare function defaultGoalBudgets(): GoalBudgets;
112
+ //# sourceMappingURL=goal-repository.d.ts.map
@@ -0,0 +1,157 @@
1
+ /**
2
+ * GoalRepository — durable storage for the ACE Goal Engine (WS-F).
3
+ *
4
+ * Persists one {@link GoalRecord} plus its active {@link TaskPlan} per goal, and
5
+ * a lightweight index for listing. Storage is plain JSON under
6
+ * `<workspaceRoot>/agent-state/goals/`, written atomically (temp + rename) so a
7
+ * crash mid-write never leaves a torn record — the property WS-F's resume path
8
+ * depends on. The repository is intentionally root-parameterized (rather than
9
+ * bound to the packed store) so the Goal Engine and its tests can drive it
10
+ * hermetically against a temp workspace.
11
+ *
12
+ * Layout:
13
+ * agent-state/goals/index.json { version, updated_at, goals: [...] }
14
+ * agent-state/goals/<goal_id>.json GoalRecord
15
+ * agent-state/goals/<goal_id>.plan.json TaskPlan (rewritten every transition)
16
+ */
17
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { randomUUID } from "node:crypto";
20
+ export function newGoalId() {
21
+ return `goal-${randomUUID()}`;
22
+ }
23
+ function nowIso() {
24
+ return new Date().toISOString();
25
+ }
26
+ export class GoalRepository {
27
+ workspaceRoot;
28
+ dir;
29
+ constructor(workspaceRoot) {
30
+ this.workspaceRoot = workspaceRoot;
31
+ this.dir = join(workspaceRoot, "agent-state", "goals");
32
+ }
33
+ ensureDir() {
34
+ if (!existsSync(this.dir)) {
35
+ mkdirSync(this.dir, { recursive: true });
36
+ }
37
+ }
38
+ goalPath(goalId) {
39
+ return join(this.dir, `${goalId}.json`);
40
+ }
41
+ planPath(goalId) {
42
+ return join(this.dir, `${goalId}.plan.json`);
43
+ }
44
+ indexPath() {
45
+ return join(this.dir, "index.json");
46
+ }
47
+ writeAtomic(path, value) {
48
+ this.ensureDir();
49
+ const tmp = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
50
+ writeFileSync(tmp, JSON.stringify(value, null, 2));
51
+ renameSync(tmp, path);
52
+ }
53
+ readJson(path) {
54
+ if (!existsSync(path))
55
+ return undefined;
56
+ try {
57
+ return JSON.parse(readFileSync(path, "utf8"));
58
+ }
59
+ catch {
60
+ return undefined;
61
+ }
62
+ }
63
+ readIndex() {
64
+ const stored = this.readJson(this.indexPath());
65
+ if (!stored || stored.version !== 1 || !Array.isArray(stored.goals)) {
66
+ return { version: 1, updated_at: nowIso(), goals: [] };
67
+ }
68
+ return stored;
69
+ }
70
+ upsertIndex(goal) {
71
+ const index = this.readIndex();
72
+ const entry = {
73
+ goal_id: goal.goal_id,
74
+ status: goal.status,
75
+ objective: goal.objective,
76
+ updated_at: goal.updated_at,
77
+ };
78
+ const next = index.goals.filter((row) => row.goal_id !== goal.goal_id);
79
+ next.push(entry);
80
+ next.sort((a, b) => (a.updated_at < b.updated_at ? 1 : -1));
81
+ this.writeAtomic(this.indexPath(), {
82
+ version: 1,
83
+ updated_at: nowIso(),
84
+ goals: next,
85
+ });
86
+ }
87
+ /** Persist a goal record and refresh the index in one call. */
88
+ saveGoal(goal) {
89
+ const record = { ...goal, updated_at: nowIso() };
90
+ this.writeAtomic(this.goalPath(record.goal_id), record);
91
+ this.upsertIndex(record);
92
+ return record;
93
+ }
94
+ getGoal(goalId) {
95
+ return this.readJson(this.goalPath(goalId));
96
+ }
97
+ savePlan(goalId, plan) {
98
+ this.writeAtomic(this.planPath(goalId), plan);
99
+ }
100
+ getPlan(goalId) {
101
+ return this.readJson(this.planPath(goalId));
102
+ }
103
+ listGoals(filter) {
104
+ return this.readIndex().goals.filter((row) => filter?.status ? row.status === filter.status : true);
105
+ }
106
+ /** Delete a goal, its plan, and its index entry. Returns true if present. */
107
+ deleteGoal(goalId) {
108
+ const goalPath = this.goalPath(goalId);
109
+ const existed = existsSync(goalPath);
110
+ for (const path of [goalPath, this.planPath(goalId)]) {
111
+ if (existsSync(path))
112
+ rmSync(path);
113
+ }
114
+ const index = this.readIndex();
115
+ this.writeAtomic(this.indexPath(), {
116
+ version: 1,
117
+ updated_at: nowIso(),
118
+ goals: index.goals.filter((row) => row.goal_id !== goalId),
119
+ });
120
+ return existed;
121
+ }
122
+ /**
123
+ * Rebuild the index from the on-disk goal files. Cheap self-heal used at
124
+ * startup so a hand-deleted or partially-written index never hides live goals.
125
+ */
126
+ rebuildIndex() {
127
+ this.ensureDir();
128
+ const goals = [];
129
+ for (const name of readdirSync(this.dir)) {
130
+ if (!name.endsWith(".json") || name.endsWith(".plan.json") || name === "index.json") {
131
+ continue;
132
+ }
133
+ const record = this.readJson(join(this.dir, name));
134
+ if (record?.goal_id) {
135
+ goals.push({
136
+ goal_id: record.goal_id,
137
+ status: record.status,
138
+ objective: record.objective,
139
+ updated_at: record.updated_at,
140
+ });
141
+ }
142
+ }
143
+ goals.sort((a, b) => (a.updated_at < b.updated_at ? 1 : -1));
144
+ const file = { version: 1, updated_at: nowIso(), goals };
145
+ this.writeAtomic(this.indexPath(), file);
146
+ return file;
147
+ }
148
+ }
149
+ export function defaultGoalBudgets() {
150
+ return {
151
+ max_wall_clock_ms: 4 * 60 * 60 * 1000, // 4h
152
+ max_ticks: 200,
153
+ max_replans: 3,
154
+ max_step_retries: 1,
155
+ };
156
+ }
157
+ //# sourceMappingURL=goal-repository.js.map
@@ -0,0 +1,107 @@
1
+ export type PlaybookSection = "strategies" | "tool_usage" | "failure_modes" | "verification" | "env_facts";
2
+ export type PlaybookBulletStatus = "active" | "quarantined" | "retired";
3
+ export interface PlaybookScope {
4
+ role?: string;
5
+ model_class?: string;
6
+ model?: string;
7
+ tool?: string;
8
+ reason_code?: string;
9
+ }
10
+ export interface PlaybookBullet {
11
+ bullet_id: string;
12
+ section: PlaybookSection;
13
+ scope: PlaybookScope;
14
+ content: string;
15
+ helpful_count: number;
16
+ harmful_count: number;
17
+ observed_count: number;
18
+ status: PlaybookBulletStatus;
19
+ evidence_refs: string[];
20
+ provenance: {
21
+ source: "reflector" | "llm_reflector" | "operator" | "migration";
22
+ run_id?: string;
23
+ };
24
+ created_at: string;
25
+ updated_at: string;
26
+ }
27
+ export type PlaybookDeltaOp = {
28
+ type: "ADD";
29
+ section: PlaybookSection;
30
+ scope: PlaybookScope;
31
+ content: string;
32
+ evidence_refs: string[];
33
+ provenance: PlaybookBullet["provenance"];
34
+ } | {
35
+ type: "TAG";
36
+ bullet_id: string;
37
+ tag: "helpful" | "harmful";
38
+ };
39
+ export interface PlaybookConfig {
40
+ max_active_total?: number;
41
+ max_active_per_section?: number;
42
+ }
43
+ export interface PlaybookIndexEntry {
44
+ bullet_id: string;
45
+ section: PlaybookSection;
46
+ status: PlaybookBulletStatus;
47
+ updated_at: string;
48
+ }
49
+ export interface PlaybookIndexFile {
50
+ version: 1;
51
+ updated_at: string;
52
+ bullets: PlaybookIndexEntry[];
53
+ }
54
+ export interface PlaybookApplyResult {
55
+ added: number;
56
+ tagged: number;
57
+ refused: Array<{
58
+ reason_code: string;
59
+ op: PlaybookDeltaOp;
60
+ }>;
61
+ }
62
+ export declare const PLAYBOOK_SECTIONS: readonly PlaybookSection[];
63
+ export declare const PLAYBOOK_STATUSES: readonly PlaybookBulletStatus[];
64
+ export declare function isPlaybookSection(value: string): value is PlaybookSection;
65
+ export declare function isPlaybookStatus(value: string): value is PlaybookBulletStatus;
66
+ export declare class PlaybookRepository {
67
+ private readonly workspaceRoot;
68
+ private readonly dir;
69
+ private migrated;
70
+ constructor(workspaceRoot: string);
71
+ get root(): string;
72
+ private useStore;
73
+ private ensureDir;
74
+ private bulletRelPath;
75
+ private bulletPath;
76
+ private indexRelPath;
77
+ private indexPath;
78
+ private markerRelPath;
79
+ private markerPath;
80
+ private artifactKey;
81
+ private writeText;
82
+ private writeJson;
83
+ private readText;
84
+ private existsRel;
85
+ private readJson;
86
+ private listBulletRelPaths;
87
+ private migrateOnce;
88
+ readIndex(): PlaybookIndexFile;
89
+ private upsertIndex;
90
+ saveBullet(bullet: PlaybookBullet): PlaybookBullet;
91
+ createBullet(input: Omit<PlaybookBullet, "bullet_id" | "helpful_count" | "harmful_count" | "observed_count" | "status" | "created_at" | "updated_at">): PlaybookBullet;
92
+ getBullet(bulletId: string): PlaybookBullet | undefined;
93
+ listBullets(filter?: {
94
+ section?: PlaybookSection;
95
+ status?: PlaybookBulletStatus;
96
+ scope?: PlaybookScope;
97
+ }): PlaybookBullet[];
98
+ applyDeltaOps(ops: PlaybookDeltaOp[], config?: PlaybookConfig): PlaybookApplyResult;
99
+ rebuildIndex(): PlaybookIndexFile;
100
+ }
101
+ export declare function parseLessonsMarkdown(markdown: string): Array<Omit<PlaybookBullet, "bullet_id" | "helpful_count" | "harmful_count" | "observed_count" | "status" | "created_at" | "updated_at">>;
102
+ export declare function renderPlaybookMarkdown(repo: PlaybookRepository, filter?: {
103
+ section?: PlaybookSection;
104
+ status?: PlaybookBulletStatus;
105
+ }): string;
106
+ export declare function materializePlaybookMarkdown(workspaceRoot: string): string;
107
+ //# sourceMappingURL=playbook-repository.d.ts.map
@@ -0,0 +1,314 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { operationalArtifactKey, writeOperationalArtifactSync, } from "../store-artifacts.js";
5
+ import { listStoreKeysSync, readStoreBlobSync, storeExistsSync, } from "../store-snapshot.js";
6
+ export const PLAYBOOK_SECTIONS = ["strategies", "tool_usage", "failure_modes", "verification", "env_facts"];
7
+ export const PLAYBOOK_STATUSES = ["active", "quarantined", "retired"];
8
+ const SECTIONS = PLAYBOOK_SECTIONS;
9
+ export function isPlaybookSection(value) {
10
+ return PLAYBOOK_SECTIONS.some((section) => section === value);
11
+ }
12
+ export function isPlaybookStatus(value) {
13
+ return PLAYBOOK_STATUSES.some((status) => status === value);
14
+ }
15
+ function nowIso() {
16
+ return new Date().toISOString();
17
+ }
18
+ function newBulletId() {
19
+ return `pb-${randomUUID().replace(/-/g, "").slice(0, 8)}`;
20
+ }
21
+ function truncateContent(content) {
22
+ return content.replace(/\s+/g, " ").trim().slice(0, 400);
23
+ }
24
+ function scopeMatches(bulletScope, filter) {
25
+ if (!filter)
26
+ return true;
27
+ return Object.entries(filter).every(([key, value]) => value === undefined || bulletScope[key] === undefined || bulletScope[key] === value);
28
+ }
29
+ export class PlaybookRepository {
30
+ workspaceRoot;
31
+ dir;
32
+ migrated = false;
33
+ constructor(workspaceRoot) {
34
+ this.workspaceRoot = workspaceRoot;
35
+ this.dir = join(workspaceRoot, "agent-state", "playbook");
36
+ }
37
+ get root() {
38
+ return this.workspaceRoot;
39
+ }
40
+ useStore() {
41
+ return storeExistsSync(this.workspaceRoot);
42
+ }
43
+ ensureDir() {
44
+ if (!this.useStore() && !existsSync(this.dir))
45
+ mkdirSync(this.dir, { recursive: true });
46
+ this.migrateOnce();
47
+ }
48
+ bulletRelPath(bulletId) {
49
+ return `agent-state/playbook/${bulletId}.json`;
50
+ }
51
+ bulletPath(bulletId) {
52
+ return join(this.workspaceRoot, this.bulletRelPath(bulletId));
53
+ }
54
+ indexRelPath() {
55
+ return "agent-state/playbook/__index.json";
56
+ }
57
+ indexPath() {
58
+ return join(this.workspaceRoot, this.indexRelPath());
59
+ }
60
+ markerRelPath() {
61
+ return "agent-state/playbook/__migrated";
62
+ }
63
+ markerPath() {
64
+ return join(this.workspaceRoot, this.markerRelPath());
65
+ }
66
+ artifactKey(relPath) {
67
+ return operationalArtifactKey(relPath);
68
+ }
69
+ writeText(relPath, content) {
70
+ if (this.useStore()) {
71
+ return writeOperationalArtifactSync(this.workspaceRoot, relPath, content);
72
+ }
73
+ const path = join(this.workspaceRoot, relPath);
74
+ mkdirSync(dirname(path), { recursive: true });
75
+ const tmp = `${path}.${process.pid}.${randomUUID().slice(0, 8)}.tmp`;
76
+ writeFileSync(tmp, content, "utf8");
77
+ renameSync(tmp, path);
78
+ return path;
79
+ }
80
+ writeJson(relPath, value) {
81
+ return this.writeText(relPath, JSON.stringify(value, null, 2));
82
+ }
83
+ readText(relPath) {
84
+ if (this.useStore()) {
85
+ const storeText = readStoreBlobSync(this.workspaceRoot, this.artifactKey(relPath));
86
+ if (typeof storeText === "string")
87
+ return storeText;
88
+ }
89
+ const path = join(this.workspaceRoot, relPath);
90
+ if (!existsSync(path))
91
+ return undefined;
92
+ return readFileSync(path, "utf8");
93
+ }
94
+ existsRel(relPath) {
95
+ if (this.useStore() && typeof readStoreBlobSync(this.workspaceRoot, this.artifactKey(relPath)) === "string") {
96
+ return true;
97
+ }
98
+ return existsSync(join(this.workspaceRoot, relPath));
99
+ }
100
+ readJson(relPath) {
101
+ const raw = this.readText(relPath);
102
+ if (typeof raw !== "string")
103
+ return undefined;
104
+ try {
105
+ return JSON.parse(raw);
106
+ }
107
+ catch {
108
+ return undefined;
109
+ }
110
+ }
111
+ listBulletRelPaths() {
112
+ const rels = new Set();
113
+ if (this.useStore()) {
114
+ const prefix = this.artifactKey("agent-state/playbook/");
115
+ for (const key of listStoreKeysSync(this.workspaceRoot, prefix)) {
116
+ const relPath = key.slice("state/artifacts/".length);
117
+ if (relPath.endsWith(".json") && !relPath.endsWith("/__index.json")) {
118
+ rels.add(relPath);
119
+ }
120
+ }
121
+ }
122
+ if (existsSync(this.dir)) {
123
+ for (const name of readdirSync(this.dir)) {
124
+ if (name.endsWith(".json") && name !== "__index.json") {
125
+ rels.add(`agent-state/playbook/${name}`);
126
+ }
127
+ }
128
+ }
129
+ return [...rels].sort((a, b) => a.localeCompare(b));
130
+ }
131
+ migrateOnce() {
132
+ if (this.migrated || this.existsRel(this.markerRelPath())) {
133
+ this.migrated = true;
134
+ return;
135
+ }
136
+ this.migrated = true;
137
+ const lessons = this.readText("agent-state/lessons.md");
138
+ if (typeof lessons === "string") {
139
+ for (const bullet of parseLessonsMarkdown(lessons)) {
140
+ this.createBullet(bullet);
141
+ }
142
+ }
143
+ this.writeText(this.markerRelPath(), `${nowIso()}\n`);
144
+ }
145
+ readIndex() {
146
+ this.ensureDir();
147
+ const stored = this.readJson(this.indexRelPath());
148
+ if (!stored || stored.version !== 1 || !Array.isArray(stored.bullets)) {
149
+ return this.rebuildIndex();
150
+ }
151
+ return stored;
152
+ }
153
+ upsertIndex(bullet) {
154
+ const index = this.readIndex();
155
+ const next = index.bullets.filter((row) => row.bullet_id !== bullet.bullet_id);
156
+ next.push({
157
+ bullet_id: bullet.bullet_id,
158
+ section: bullet.section,
159
+ status: bullet.status,
160
+ updated_at: bullet.updated_at,
161
+ });
162
+ next.sort((a, b) => (a.updated_at < b.updated_at ? 1 : -1));
163
+ this.writeJson(this.indexRelPath(), { version: 1, updated_at: nowIso(), bullets: next });
164
+ }
165
+ saveBullet(bullet) {
166
+ this.ensureDir();
167
+ const record = { ...bullet, content: truncateContent(bullet.content), updated_at: nowIso() };
168
+ this.writeJson(this.bulletRelPath(record.bullet_id), record);
169
+ this.upsertIndex(record);
170
+ return record;
171
+ }
172
+ createBullet(input) {
173
+ const at = nowIso();
174
+ return this.saveBullet({
175
+ bullet_id: newBulletId(),
176
+ section: input.section,
177
+ scope: input.scope,
178
+ content: input.content,
179
+ helpful_count: 0,
180
+ harmful_count: 0,
181
+ observed_count: input.provenance.source === "llm_reflector" ? 0 : 1,
182
+ // Evidence-free notes from every producer are retained but cannot be injected
183
+ // until evidence is attached through a curated path.
184
+ status: input.evidence_refs.length === 0 ? "quarantined" : "active",
185
+ evidence_refs: input.evidence_refs.slice(0, 5),
186
+ provenance: input.provenance,
187
+ created_at: at,
188
+ updated_at: at,
189
+ });
190
+ }
191
+ getBullet(bulletId) {
192
+ this.ensureDir();
193
+ return this.readJson(this.bulletRelPath(bulletId));
194
+ }
195
+ listBullets(filter) {
196
+ return this.readIndex().bullets
197
+ .filter((row) => (!filter?.section || row.section === filter.section) && (!filter?.status || row.status === filter.status))
198
+ .flatMap((row) => {
199
+ const bullet = this.getBullet(row.bullet_id);
200
+ return bullet && scopeMatches(bullet.scope, filter?.scope) ? [bullet] : [];
201
+ });
202
+ }
203
+ applyDeltaOps(ops, config = {}) {
204
+ const result = { added: 0, tagged: 0, refused: [] };
205
+ for (const op of ops) {
206
+ if (op.type === "TAG") {
207
+ const bullet = this.getBullet(op.bullet_id);
208
+ if (!bullet)
209
+ continue;
210
+ this.saveBullet({
211
+ ...bullet,
212
+ helpful_count: bullet.helpful_count + (op.tag === "helpful" ? 1 : 0),
213
+ harmful_count: bullet.harmful_count + (op.tag === "harmful" ? 1 : 0),
214
+ });
215
+ result.tagged += 1;
216
+ continue;
217
+ }
218
+ const active = this.listBullets({ status: "active", section: op.section });
219
+ const perSectionCap = config.max_active_per_section ?? 64;
220
+ const totalCap = config.max_active_total ?? 200;
221
+ if (active.length >= perSectionCap || this.listBullets({ status: "active" }).length >= totalCap) {
222
+ result.refused.push({ reason_code: "playbook_section_full", op });
223
+ continue;
224
+ }
225
+ this.createBullet(op);
226
+ result.added += 1;
227
+ }
228
+ return result;
229
+ }
230
+ rebuildIndex() {
231
+ if (!this.useStore() && !existsSync(this.dir))
232
+ mkdirSync(this.dir, { recursive: true });
233
+ const bullets = [];
234
+ for (const relPath of this.listBulletRelPaths()) {
235
+ const bullet = this.readJson(relPath);
236
+ if (bullet?.bullet_id && SECTIONS.includes(bullet.section)) {
237
+ bullets.push({
238
+ bullet_id: bullet.bullet_id,
239
+ section: bullet.section,
240
+ status: bullet.status,
241
+ updated_at: bullet.updated_at,
242
+ });
243
+ }
244
+ }
245
+ bullets.sort((a, b) => (a.updated_at < b.updated_at ? 1 : -1));
246
+ const file = { version: 1, updated_at: nowIso(), bullets };
247
+ this.writeJson(this.indexRelPath(), file);
248
+ return file;
249
+ }
250
+ }
251
+ export function parseLessonsMarkdown(markdown) {
252
+ return markdown
253
+ .split(/^## Lesson\s+.*$/m)
254
+ .map((block) => block.trim())
255
+ .filter(Boolean)
256
+ .map((block) => {
257
+ const field = (name) => {
258
+ const match = block.match(new RegExp(`(?:^|\\n)\\*\\*${name}:\\*\\*\\s*([\\s\\S]*?)(?=\\n\\*\\*|$)`, "i"));
259
+ return match?.[1]?.trim() ?? "";
260
+ };
261
+ const guardrail = field("Guardrail");
262
+ const verification = field("Verification Check");
263
+ return {
264
+ section: "failure_modes",
265
+ scope: {},
266
+ content: truncateContent(`${guardrail}${verification ? ` Verify: ${verification}` : ""}`),
267
+ evidence_refs: [],
268
+ provenance: { source: "migration" },
269
+ };
270
+ })
271
+ .filter((bullet) => bullet.content.length > 0);
272
+ }
273
+ export function renderPlaybookMarkdown(repo, filter = {}) {
274
+ const bullets = repo
275
+ .listBullets(filter)
276
+ .sort((a, b) => a.section.localeCompare(b.section) ||
277
+ a.status.localeCompare(b.status) ||
278
+ a.bullet_id.localeCompare(b.bullet_id));
279
+ const lines = ["# PLAYBOOK", "", "_Derived from ACEPACK-backed `agent-state/playbook/*.json`; regenerate rather than hand-edit._", ""];
280
+ for (const status of ["active", "quarantined", "retired"]) {
281
+ const statusBullets = bullets.filter((bullet) => bullet.status === status);
282
+ if (statusBullets.length === 0)
283
+ continue;
284
+ lines.push(`## ${status}`, "");
285
+ for (const section of SECTIONS) {
286
+ const sectionBullets = statusBullets.filter((bullet) => bullet.section === section);
287
+ if (sectionBullets.length === 0)
288
+ continue;
289
+ lines.push(`### ${section}`, "");
290
+ for (const bullet of sectionBullets) {
291
+ const scope = Object.keys(bullet.scope).length > 0 ? ` scope=${JSON.stringify(bullet.scope)}` : "";
292
+ lines.push(`- [${bullet.bullet_id}] ${bullet.content} ` +
293
+ `(helpful=${bullet.helpful_count}, harmful=${bullet.harmful_count}, observed=${bullet.observed_count}${scope})`);
294
+ }
295
+ lines.push("");
296
+ }
297
+ }
298
+ if (bullets.length === 0) {
299
+ lines.push("_No playbook bullets recorded._", "");
300
+ }
301
+ return lines.join("\n");
302
+ }
303
+ export function materializePlaybookMarkdown(workspaceRoot) {
304
+ const repo = new PlaybookRepository(workspaceRoot);
305
+ const relPath = "agent-state/PLAYBOOK.md";
306
+ if (storeExistsSync(workspaceRoot)) {
307
+ return writeOperationalArtifactSync(workspaceRoot, relPath, renderPlaybookMarkdown(repo));
308
+ }
309
+ const path = join(workspaceRoot, relPath);
310
+ mkdirSync(join(workspaceRoot, "agent-state"), { recursive: true });
311
+ writeFileSync(path, renderPlaybookMarkdown(repo), "utf8");
312
+ return path;
313
+ }
314
+ //# sourceMappingURL=playbook-repository.js.map