@trycedar/argus 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - 2026-05-13
4
+
5
+ Initial dogfood release candidate. Published as `@trycedar/argus` (codename Argus). The CLI binary is `proactive` in this release and will be renamed to `argus` in 0.2.0.
6
+
7
+ ### Added
8
+
9
+ - `proactive` CLI package skeleton.
10
+ - Workspace initialization with `proactive init`.
11
+ - CEO instruction capture with `proactive ceo`.
12
+ - Task, agent, inbox, and event listing/management commands.
13
+ - Local-first Markdown/frontmatter workspace models.
14
+ - Append-only JSONL event log.
15
+ - Agent wake planning and bounded agent runner.
16
+ - Codex provider behind an `AIProvider` abstraction.
17
+ - Stub provider with self-dogfood and property-management scenarios.
18
+ - Dry-run previews, duplicate action detection, and one-agent default for write-producing runs.
19
+ - Human inbox approve/reject flow.
20
+ - Property-management demo workspace under `demos/property-management`.
21
+
22
+ ### Safety
23
+
24
+ - Comment actions enforce `can_comment` permissions.
25
+ - Artifact creation refuses to overwrite existing files.
26
+ - Scanner emits deletion events for tracked workspace files.
27
+ - CI validation sets up Node and pnpm before running checks.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # Proactive AI
2
+
3
+ > A Git-native agentic organization framework where a human CEO directs proactive role agents that create tasks, produce artifacts, request approvals, and help build software products.
4
+
5
+ ## Vision
6
+
7
+ Proactive AI is not multi-agent chat. It is a local-first company workspace where agents participate proactively in software development.
8
+
9
+ - The human is the CEO.
10
+ - Agents have roles, responsibilities, permissions, and wake triggers.
11
+ - Memory lives in version-controlled Markdown artifacts.
12
+ - Tasks are Markdown files with structured frontmatter.
13
+ - Human approvals flow through an inbox.
14
+ - Events are appended to an audit log.
15
+ - Codex is the default AI provider for v0.
16
+
17
+ ## Current status
18
+
19
+ This repository has an initial v0 CLI/runtime foundation and dogfoods Proactive AI against itself. `0.1.0` is a local-first release candidate; publishing still requires explicit human approval.
20
+
21
+ Key docs:
22
+
23
+ - `docs/prd-v0.md`
24
+ - `docs/technical-design-v0.md`
25
+ - `docs/architecture-v0.md`
26
+ - `docs/self-hosting-and-release.md`
27
+ - `docs/agent-system-and-harness.md`
28
+ - `docs/pi-project-template-evaluation.md`
29
+
30
+ ## Dogfood workspace
31
+
32
+ This repo includes the initial Proactive AI workspace structure:
33
+
34
+ ```text
35
+ .proactive/
36
+ company/
37
+ tasks/
38
+ artifacts/
39
+ comments/
40
+ inbox/
41
+ events/
42
+ agents/
43
+ ```
44
+
45
+ The workspace tracks the development of Proactive AI itself.
46
+
47
+ ## Development harness
48
+
49
+ This repo uses `pi-project-template` conventions:
50
+
51
+ - `AGENTS.md` — hard rules for agents
52
+ - `docs/memory/` — short/long-term shared memory
53
+ - `docs/plans/` — active plans
54
+ - `docs/records/` — completed records
55
+ - `docs/decisions/` — ADRs
56
+ - `docs/log.md` — append-only change log
57
+ - `.github/workflows/codex.yml` — Codex issue pickup workflow
58
+ - `scripts/validate.sh` — merge validation contract
59
+
60
+ ## Install
61
+
62
+ From source:
63
+
64
+ ```bash
65
+ pnpm install
66
+ pnpm build
67
+ pnpm link --global
68
+ proactive --help
69
+ ```
70
+
71
+ After npm publication:
72
+
73
+ ```bash
74
+ npm install -g @trycedar/argus
75
+ proactive --help
76
+ ```
77
+
78
+ ## CLI
79
+
80
+ The initial CLI/runtime foundation is implemented.
81
+
82
+ ```bash
83
+ pnpm install
84
+ pnpm exec tsx src/cli/index.ts init demo-company
85
+ pnpm exec tsx src/cli/index.ts ceo "Build a property management SaaS"
86
+ pnpm exec tsx src/cli/index.ts run --no-ai
87
+ pnpm exec tsx src/cli/index.ts run --provider stub
88
+ pnpm exec tsx src/cli/index.ts tasks
89
+ pnpm exec tsx src/cli/index.ts inbox
90
+ pnpm exec tsx src/cli/index.ts approve <item-id> --response "Approved"
91
+ pnpm exec tsx src/cli/index.ts reject <item-id> --response "Rejected"
92
+ pnpm exec tsx src/cli/index.ts agents
93
+ ```
94
+
95
+ `proactive run --no-ai` scans the workspace and plans agent wakeups without invoking Codex. `proactive run --provider stub` performs a controlled dogfood run with deterministic stubbed agent output. Running without `--no-ai` uses the default `CodexProvider`, which shells out to `codex exec`; set `PROACTIVE_CODEX_COMMAND` if the executable name differs.
96
+
97
+ ## Property-management demo
98
+
99
+ A deterministic external demo workspace lives at `demos/property-management`.
100
+
101
+ To recreate it:
102
+
103
+ ```bash
104
+ rm -rf demos/property-management
105
+ pnpm build
106
+ pnpm exec tsx src/cli/index.ts init property-management-demo --root demos/property-management
107
+ cd demos/property-management
108
+ node ../../dist/index.js ceo "We are building property management software for landlords, property managers, and tenants. Find the best MVP."
109
+ node ../../dist/index.js run --no-ai
110
+ node ../../dist/index.js run --provider stub --scenario property-management --max-agents 4
111
+ node ../../dist/index.js run --provider stub --scenario property-management --max-agents 4
112
+ ```
113
+
114
+ Expected outputs include property-management MVP tasks, an engineering feasibility artifact, and a CEO decision request.
115
+
116
+ ## Validation
117
+
118
+ ```bash
119
+ ./scripts/validate.sh --check
120
+ ```
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,1188 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { Command } from "commander";
5
+ import path12 from "path";
6
+ import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile7 } from "fs/promises";
7
+
8
+ // src/workspace/init.ts
9
+ import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
10
+ import path3 from "path";
11
+ import YAML from "yaml";
12
+
13
+ // src/markdown/frontmatter.ts
14
+ import matter from "gray-matter";
15
+ function parseMarkdown(raw, parseData) {
16
+ const parsed = matter(raw);
17
+ return {
18
+ data: parseData(parsed.data),
19
+ content: parsed.content.trimStart()
20
+ };
21
+ }
22
+ function stringifyMarkdown(data, content) {
23
+ return matter.stringify(content.trimStart(), dropUndefined(data)).trimEnd() + "\n";
24
+ }
25
+ function dropUndefined(data) {
26
+ return Object.fromEntries(
27
+ Object.entries(data).filter(([, value]) => value !== void 0)
28
+ );
29
+ }
30
+
31
+ // src/events/event-log.ts
32
+ import { mkdir, readFile, writeFile } from "fs/promises";
33
+ import path from "path";
34
+
35
+ // src/schemas/entities.ts
36
+ import { z } from "zod";
37
+ var dateStringSchema = z.preprocess((value) => {
38
+ if (value instanceof Date) return value.toISOString();
39
+ return value;
40
+ }, z.string().min(1));
41
+ var taskStatusSchema = z.enum([
42
+ "backlog",
43
+ "ready",
44
+ "in_progress",
45
+ "review",
46
+ "done",
47
+ "blocked",
48
+ "cancelled"
49
+ ]);
50
+ var prioritySchema = z.enum(["low", "medium", "high", "critical"]);
51
+ var agentSchema = z.object({
52
+ id: z.string().min(1),
53
+ name: z.string().min(1),
54
+ role: z.string().min(1),
55
+ status: z.enum(["active", "paused"]).default("active"),
56
+ wake_triggers: z.array(z.string()).default([]),
57
+ can_edit: z.array(z.string()).default([]),
58
+ can_comment: z.array(z.string()).default([]),
59
+ requires_approval: z.array(z.string()).default([])
60
+ });
61
+ var taskSchema = z.object({
62
+ id: z.string().min(1),
63
+ title: z.string().min(1),
64
+ status: taskStatusSchema,
65
+ priority: prioritySchema.default("medium"),
66
+ owner: z.string().optional(),
67
+ created_by: z.string().min(1),
68
+ created_at: dateStringSchema,
69
+ updated_at: dateStringSchema,
70
+ mentions: z.array(z.string()).default([]),
71
+ depends_on: z.array(z.string()).default([]),
72
+ artifacts: z.array(z.string()).default([]),
73
+ approval_required: z.boolean().default(false),
74
+ reviewer: z.string().optional()
75
+ });
76
+ var inboxItemSchema = z.object({
77
+ id: z.string().min(1),
78
+ type: z.enum(["decision_request", "approval_request", "digest"]),
79
+ status: z.enum(["awaiting_human", "approved", "rejected", "closed"]),
80
+ priority: prioritySchema.default("medium"),
81
+ requested_by: z.string().min(1),
82
+ related_task: z.string().optional(),
83
+ related_artifacts: z.array(z.string()).default([]),
84
+ created_at: dateStringSchema,
85
+ updated_at: dateStringSchema.optional()
86
+ });
87
+ var eventSchema = z.object({
88
+ id: z.string().min(1),
89
+ type: z.string().min(1),
90
+ actor: z.string().min(1),
91
+ timestamp: dateStringSchema,
92
+ summary: z.string().min(1),
93
+ task: z.string().optional(),
94
+ files: z.array(z.string()).optional()
95
+ });
96
+ var workspaceStateSchema = z.object({
97
+ last_event_id: z.string().default("evt-000"),
98
+ last_scan_at: dateStringSchema.default(""),
99
+ file_hashes: z.record(z.string(), z.string()).default({}),
100
+ next_ids: z.object({
101
+ task: z.number().int().positive().default(1),
102
+ event: z.number().int().positive().default(1),
103
+ decision: z.number().int().positive().default(1),
104
+ approval: z.number().int().positive().default(1)
105
+ })
106
+ });
107
+
108
+ // src/events/event-log.ts
109
+ function formatEventId(next) {
110
+ return `evt-${String(next).padStart(3, "0")}`;
111
+ }
112
+ async function appendEvent(workspaceRoot, event) {
113
+ const eventPath = path.join(workspaceRoot, "events", "events.jsonl");
114
+ await mkdir(path.dirname(eventPath), { recursive: true });
115
+ const parsed = eventSchema.parse(event);
116
+ await writeFile(eventPath, `${JSON.stringify(parsed)}
117
+ `, { flag: "a" });
118
+ }
119
+ async function readEvents(workspaceRoot) {
120
+ const eventPath = path.join(workspaceRoot, "events", "events.jsonl");
121
+ try {
122
+ const raw = await readFile(eventPath, "utf8");
123
+ return raw.split("\n").filter(Boolean).map((line) => eventSchema.parse(JSON.parse(line)));
124
+ } catch (error) {
125
+ if (error.code === "ENOENT") return [];
126
+ throw error;
127
+ }
128
+ }
129
+
130
+ // src/workspace/state.ts
131
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
132
+ import path2 from "path";
133
+ function initialState() {
134
+ return {
135
+ last_event_id: "evt-001",
136
+ last_scan_at: (/* @__PURE__ */ new Date(0)).toISOString(),
137
+ file_hashes: {},
138
+ next_ids: { task: 1, event: 2, decision: 1, approval: 1 }
139
+ };
140
+ }
141
+ async function readState(workspaceRoot) {
142
+ const statePath = path2.join(workspaceRoot, ".proactive", "state.json");
143
+ try {
144
+ return workspaceStateSchema.parse(
145
+ JSON.parse(await readFile2(statePath, "utf8"))
146
+ );
147
+ } catch (error) {
148
+ if (error.code === "ENOENT")
149
+ return initialState();
150
+ throw error;
151
+ }
152
+ }
153
+ async function writeState(workspaceRoot, state) {
154
+ const statePath = path2.join(workspaceRoot, ".proactive", "state.json");
155
+ await writeFile2(
156
+ statePath,
157
+ `${JSON.stringify(workspaceStateSchema.parse(state), null, 2)}
158
+ `
159
+ );
160
+ }
161
+ function allocateId(state, kind, prefix) {
162
+ const id = `${prefix}-${String(state.next_ids[kind]).padStart(3, "0")}`;
163
+ state.next_ids[kind] += 1;
164
+ return id;
165
+ }
166
+
167
+ // src/workspace/templates.ts
168
+ var defaultAgents = [
169
+ {
170
+ filename: "chief-of-staff-agent.md",
171
+ data: {
172
+ id: "chief-of-staff-agent",
173
+ name: "Chief of Staff Agent",
174
+ role: "Chief of Staff",
175
+ status: "active",
176
+ wake_triggers: [
177
+ "ceo_instruction_created",
178
+ "task_status_changed",
179
+ "approval_requested"
180
+ ],
181
+ can_edit: ["/tasks/**", "/inbox/human/**", "/artifacts/**"],
182
+ can_comment: ["/**"],
183
+ requires_approval: ["company_strategy_change", "schema_change"]
184
+ },
185
+ body: "## Responsibilities\n\n- Coordinate active work.\n- Detect blockers.\n- Prepare CEO digests.\n"
186
+ },
187
+ {
188
+ filename: "strategy-agent.md",
189
+ data: {
190
+ id: "strategy-agent",
191
+ name: "Strategy Agent",
192
+ role: "Strategy",
193
+ status: "active",
194
+ wake_triggers: ["ceo_instruction_created", "strategy_artifact_changed"],
195
+ can_edit: [
196
+ "/tasks/**",
197
+ "/artifacts/research/**",
198
+ "/artifacts/marketing/**"
199
+ ],
200
+ can_comment: ["/company/**", "/artifacts/**"],
201
+ requires_approval: ["company_strategy_change", "positioning_change"]
202
+ },
203
+ body: "## Responsibilities\n\n- Clarify positioning.\n- Identify market assumptions and risks.\n"
204
+ },
205
+ {
206
+ filename: "pm-agent.md",
207
+ data: {
208
+ id: "pm-agent",
209
+ name: "Product Manager Agent",
210
+ role: "Product Manager",
211
+ status: "active",
212
+ wake_triggers: [
213
+ "ceo_instruction_created",
214
+ "product_artifact_changed",
215
+ "task_assigned",
216
+ "agent_mentioned"
217
+ ],
218
+ can_edit: ["/tasks/**", "/artifacts/product/**"],
219
+ can_comment: ["/artifacts/engineering/**", "/company/**"],
220
+ requires_approval: ["mvp_scope_finalization", "company_strategy_change"]
221
+ },
222
+ body: "## Responsibilities\n\n- Maintain product scope.\n- Draft PRDs.\n- Convert strategy into implementation tasks.\n"
223
+ },
224
+ {
225
+ filename: "engineer-agent.md",
226
+ data: {
227
+ id: "engineer-agent",
228
+ name: "Engineer Agent",
229
+ role: "Engineer",
230
+ status: "active",
231
+ wake_triggers: [
232
+ "engineering_task_created",
233
+ "task_assigned",
234
+ "agent_mentioned"
235
+ ],
236
+ can_edit: [
237
+ "/tasks/**",
238
+ "/artifacts/engineering/**",
239
+ "/src/**",
240
+ "/tests/**"
241
+ ],
242
+ can_comment: ["/artifacts/product/**", "/company/**"],
243
+ requires_approval: [
244
+ "schema_change",
245
+ "default_provider_change",
246
+ "external_side_effect",
247
+ "release_publish"
248
+ ]
249
+ },
250
+ body: "## Responsibilities\n\n- Design implementation plans.\n- Implement approved engineering tasks.\n- Run validation.\n"
251
+ }
252
+ ];
253
+ function missionTemplate(workspaceName) {
254
+ return `# Mission
255
+
256
+ Build ${workspaceName} with a proactive AI workspace.
257
+
258
+ ## Current Objective
259
+
260
+ Define the initial direction and let agents create tasks, artifacts, and human decision requests.
261
+ `;
262
+ }
263
+ var strategyTemplate = `# Strategy
264
+
265
+ ## Positioning
266
+
267
+ _TBD by the human CEO and Strategy Agent._
268
+ `;
269
+ var decisionsTemplate = `# Decisions
270
+
271
+ `;
272
+
273
+ // src/workspace/init.ts
274
+ var directories = [
275
+ ".proactive",
276
+ "company",
277
+ "tasks/backlog",
278
+ "tasks/ready",
279
+ "tasks/in-progress",
280
+ "tasks/review",
281
+ "tasks/done",
282
+ "tasks/blocked",
283
+ "tasks/cancelled",
284
+ "artifacts/product",
285
+ "artifacts/engineering",
286
+ "artifacts/research",
287
+ "artifacts/marketing",
288
+ "comments",
289
+ "inbox/human",
290
+ "events",
291
+ "agents"
292
+ ];
293
+ async function initializeWorkspace(workspaceRoot, workspaceName) {
294
+ for (const directory of directories) {
295
+ await mkdir2(path3.join(workspaceRoot, directory), { recursive: true });
296
+ }
297
+ await writeFile3(
298
+ path3.join(workspaceRoot, ".proactive", "config.yaml"),
299
+ YAML.stringify({
300
+ workspace_name: workspaceName,
301
+ schema_version: 0,
302
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
303
+ default_ai_provider: "codex",
304
+ human_actor: "human-ceo"
305
+ })
306
+ );
307
+ await writeState(workspaceRoot, initialState());
308
+ await writeFile3(
309
+ path3.join(workspaceRoot, "company", "mission.md"),
310
+ missionTemplate(workspaceName)
311
+ );
312
+ await writeFile3(
313
+ path3.join(workspaceRoot, "company", "strategy.md"),
314
+ strategyTemplate
315
+ );
316
+ await writeFile3(
317
+ path3.join(workspaceRoot, "company", "decisions.md"),
318
+ decisionsTemplate
319
+ );
320
+ for (const agent of defaultAgents) {
321
+ await writeFile3(
322
+ path3.join(workspaceRoot, "agents", agent.filename),
323
+ stringifyMarkdown(agent.data, agent.body)
324
+ );
325
+ }
326
+ const event = {
327
+ id: "evt-001",
328
+ type: "workspace.created",
329
+ actor: "human-ceo",
330
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
331
+ summary: `Initialized workspace ${workspaceName}.`
332
+ };
333
+ await appendEvent(workspaceRoot, event);
334
+ }
335
+
336
+ // src/workspace/paths.ts
337
+ import path4 from "path";
338
+ var taskStatusToDirectory = {
339
+ backlog: "backlog",
340
+ ready: "ready",
341
+ in_progress: "in-progress",
342
+ review: "review",
343
+ done: "done",
344
+ blocked: "blocked",
345
+ cancelled: "cancelled"
346
+ };
347
+ function resolveWorkspace(root = process.cwd()) {
348
+ return path4.resolve(root);
349
+ }
350
+
351
+ // src/tasks/task-store.ts
352
+ import { mkdir as mkdir3, readFile as readFile3, rename, writeFile as writeFile4 } from "fs/promises";
353
+ import path5 from "path";
354
+ import fg from "fast-glob";
355
+ function taskFilename(task) {
356
+ const slug = task.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
357
+ return `${task.id}-${slug || "task"}.md`;
358
+ }
359
+ async function listTasks(workspaceRoot) {
360
+ const files = await fg("tasks/**/*.md", {
361
+ cwd: workspaceRoot,
362
+ onlyFiles: true
363
+ });
364
+ const tasks = await Promise.all(
365
+ files.map((file) => readTaskAt(workspaceRoot, file))
366
+ );
367
+ return tasks.sort((a, b) => a.task.id.localeCompare(b.task.id));
368
+ }
369
+ async function readTaskAt(workspaceRoot, relativePath) {
370
+ const raw = await readFile3(path5.join(workspaceRoot, relativePath), "utf8");
371
+ const parsed = parseMarkdown(raw, (data) => taskSchema.parse(data));
372
+ return { task: parsed.data, content: parsed.content, path: relativePath };
373
+ }
374
+ async function writeTask(workspaceRoot, task, content) {
375
+ const parsed = taskSchema.parse(task);
376
+ const directory = path5.join("tasks", taskStatusToDirectory[parsed.status]);
377
+ await mkdir3(path5.join(workspaceRoot, directory), { recursive: true });
378
+ const relativePath = path5.join(directory, taskFilename(parsed));
379
+ await writeFile4(
380
+ path5.join(workspaceRoot, relativePath),
381
+ stringifyMarkdown(parsed, content)
382
+ );
383
+ return relativePath;
384
+ }
385
+
386
+ // src/inbox/inbox-store.ts
387
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
388
+ import path6 from "path";
389
+ import fg2 from "fast-glob";
390
+ function inboxFilename(item) {
391
+ return `${item.id}.md`;
392
+ }
393
+ async function listInboxItems(workspaceRoot) {
394
+ const files = await fg2("inbox/human/*.md", {
395
+ cwd: workspaceRoot,
396
+ onlyFiles: true
397
+ });
398
+ const items = await Promise.all(
399
+ files.map((file) => readInboxAt(workspaceRoot, file))
400
+ );
401
+ return items.sort((a, b) => a.item.id.localeCompare(b.item.id));
402
+ }
403
+ async function findInboxItem(workspaceRoot, itemId) {
404
+ return (await listInboxItems(workspaceRoot)).find(
405
+ (item) => item.item.id === itemId
406
+ );
407
+ }
408
+ async function readInboxAt(workspaceRoot, relativePath) {
409
+ const raw = await readFile4(path6.join(workspaceRoot, relativePath), "utf8");
410
+ const parsed = parseMarkdown(raw, (data) => inboxItemSchema.parse(data));
411
+ return { item: parsed.data, content: parsed.content, path: relativePath };
412
+ }
413
+ async function writeInboxItem(workspaceRoot, item, content) {
414
+ const parsed = inboxItemSchema.parse(item);
415
+ await mkdir4(path6.join(workspaceRoot, "inbox", "human"), { recursive: true });
416
+ const relativePath = path6.join("inbox", "human", inboxFilename(parsed));
417
+ await writeFile5(
418
+ path6.join(workspaceRoot, relativePath),
419
+ stringifyMarkdown(parsed, content)
420
+ );
421
+ return relativePath;
422
+ }
423
+ async function respondToInboxItem(workspaceRoot, doc, status, response) {
424
+ await writeInboxItem(
425
+ workspaceRoot,
426
+ { ...doc.item, status, updated_at: (/* @__PURE__ */ new Date()).toISOString() },
427
+ `${doc.content.trimEnd()}
428
+
429
+ ## CEO Response
430
+
431
+ ${response}
432
+ `
433
+ );
434
+ }
435
+
436
+ // src/agents/agent-store.ts
437
+ import { readFile as readFile5 } from "fs/promises";
438
+ import path7 from "path";
439
+ import fg3 from "fast-glob";
440
+ async function listAgents(workspaceRoot) {
441
+ const files = await fg3("agents/*.md", {
442
+ cwd: workspaceRoot,
443
+ onlyFiles: true
444
+ });
445
+ const agents = await Promise.all(
446
+ files.map((file) => readAgentAt(workspaceRoot, file))
447
+ );
448
+ return agents.sort((a, b) => a.agent.id.localeCompare(b.agent.id));
449
+ }
450
+ async function readAgentAt(workspaceRoot, relativePath) {
451
+ const raw = await readFile5(path7.join(workspaceRoot, relativePath), "utf8");
452
+ const parsed = parseMarkdown(raw, (data) => agentSchema.parse(data));
453
+ return {
454
+ agent: parsed.data,
455
+ instructions: parsed.content,
456
+ path: relativePath
457
+ };
458
+ }
459
+
460
+ // src/workspace/scanner.ts
461
+ import { createHash } from "crypto";
462
+ import { readFile as readFile6 } from "fs/promises";
463
+ import fg4 from "fast-glob";
464
+ var scanPatterns = [
465
+ "tasks/**/*.md",
466
+ "artifacts/**/*.md",
467
+ "inbox/human/*.md",
468
+ "company/*.md",
469
+ "agents/*.md"
470
+ ];
471
+ async function hashWorkspaceFile(workspaceRoot, file) {
472
+ const raw = await readFile6(`${workspaceRoot}/${file}`);
473
+ return createHash("sha256").update(raw).digest("hex");
474
+ }
475
+ async function detectWorkspaceChanges(workspaceRoot) {
476
+ const state = await readState(workspaceRoot);
477
+ const files = await fg4(scanPatterns, { cwd: workspaceRoot, onlyFiles: true });
478
+ const nextHashes = {};
479
+ let changeCount = 0;
480
+ for (const file of files.sort()) {
481
+ const hash = await hashWorkspaceFile(workspaceRoot, file);
482
+ nextHashes[file] = hash;
483
+ if (state.file_hashes[file] && state.file_hashes[file] !== hash) {
484
+ await appendEvent(workspaceRoot, {
485
+ id: formatEventId(state.next_ids.event++),
486
+ type: semanticType(file, false),
487
+ actor: "orchestrator",
488
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
489
+ summary: `Detected update to ${file}.`,
490
+ files: [file]
491
+ });
492
+ changeCount += 1;
493
+ } else if (!state.file_hashes[file]) {
494
+ await appendEvent(workspaceRoot, {
495
+ id: formatEventId(state.next_ids.event++),
496
+ type: semanticType(file, true),
497
+ actor: "orchestrator",
498
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
499
+ summary: `Detected new file ${file}.`,
500
+ files: [file]
501
+ });
502
+ changeCount += 1;
503
+ }
504
+ }
505
+ for (const deletedFile of Object.keys(state.file_hashes).filter((file) => !(file in nextHashes)).sort()) {
506
+ await appendEvent(workspaceRoot, {
507
+ id: formatEventId(state.next_ids.event++),
508
+ type: semanticType(deletedFile, false).replace(/\.updated$/, ".deleted"),
509
+ actor: "orchestrator",
510
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
511
+ summary: `Detected deletion of ${deletedFile}.`,
512
+ files: [deletedFile]
513
+ });
514
+ changeCount += 1;
515
+ }
516
+ state.file_hashes = nextHashes;
517
+ state.last_scan_at = (/* @__PURE__ */ new Date()).toISOString();
518
+ state.last_event_id = formatEventId(Math.max(1, state.next_ids.event - 1));
519
+ await writeState(workspaceRoot, state);
520
+ return changeCount;
521
+ }
522
+ function semanticType(file, created) {
523
+ if (file.startsWith("tasks/"))
524
+ return created ? "task.created" : "task.updated";
525
+ if (file.startsWith("artifacts/"))
526
+ return created ? "artifact.created" : "artifact.updated";
527
+ if (file.startsWith("inbox/"))
528
+ return created ? "approval.requested" : "approval.updated";
529
+ if (file.startsWith("agents/"))
530
+ return created ? "agent.created" : "agent.updated";
531
+ return created ? "file.created" : "file.updated";
532
+ }
533
+
534
+ // src/agents/wake-planner.ts
535
+ function planAgentWakeups(agents, events, tasks) {
536
+ const recentTypes = new Set(
537
+ events.slice(-20).map((event) => event.type.replaceAll(".", "_"))
538
+ );
539
+ return agents.filter((agentDoc) => {
540
+ if (agentDoc.agent.status !== "active") return false;
541
+ if (agentDoc.agent.wake_triggers.some((trigger) => recentTypes.has(trigger)))
542
+ return true;
543
+ if (tasks.some(
544
+ (task) => task.task.owner === agentDoc.agent.id && ["ready", "in_progress"].includes(task.task.status)
545
+ )) {
546
+ return true;
547
+ }
548
+ if (tasks.some((task) => task.task.mentions.includes(agentDoc.agent.id)))
549
+ return true;
550
+ return false;
551
+ });
552
+ }
553
+
554
+ // src/agents/runner.ts
555
+ import { access, mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
556
+ import path10 from "path";
557
+
558
+ // src/agents/actions.ts
559
+ import { z as z2 } from "zod";
560
+ var agentActionSchema = z2.discriminatedUnion("type", [
561
+ z2.object({
562
+ type: z2.literal("create_task"),
563
+ title: z2.string().min(1),
564
+ priority: z2.enum(["low", "medium", "high", "critical"]).default("medium"),
565
+ owner: z2.string().optional(),
566
+ content: z2.string().min(1),
567
+ mentions: z2.array(z2.string()).default([])
568
+ }),
569
+ z2.object({
570
+ type: z2.literal("create_artifact"),
571
+ path: z2.string().min(1),
572
+ content: z2.string().min(1)
573
+ }),
574
+ z2.object({
575
+ type: z2.literal("create_inbox_item"),
576
+ kind: z2.enum(["decision_request", "approval_request"]),
577
+ title: z2.string().min(1),
578
+ priority: z2.enum(["low", "medium", "high", "critical"]).default("medium"),
579
+ content: z2.string().min(1),
580
+ related_task: z2.string().optional()
581
+ }),
582
+ z2.object({
583
+ type: z2.literal("comment"),
584
+ path: z2.string().min(1),
585
+ content: z2.string().min(1)
586
+ })
587
+ ]);
588
+ var agentOutputSchema = z2.object({
589
+ thought_summary: z2.string().min(1),
590
+ actions: z2.array(agentActionSchema).default([])
591
+ });
592
+ function parseAgentOutput(raw) {
593
+ const jsonMatch = raw.match(/```json\s*([\s\S]*?)```/) ?? raw.match(/({[\s\S]*})/);
594
+ const json = jsonMatch ? jsonMatch[1] : raw;
595
+ return agentOutputSchema.parse(JSON.parse(json));
596
+ }
597
+
598
+ // src/agents/context.ts
599
+ import { readFile as readFile7 } from "fs/promises";
600
+ import path8 from "path";
601
+ async function readOptional(workspaceRoot, relativePath) {
602
+ try {
603
+ return await readFile7(path8.join(workspaceRoot, relativePath), "utf8");
604
+ } catch (error) {
605
+ if (error.code === "ENOENT") return "";
606
+ throw error;
607
+ }
608
+ }
609
+ async function buildAgentPrompt(workspaceRoot, agentDoc) {
610
+ const [mission, strategy, decisions, events, tasks] = await Promise.all([
611
+ readOptional(workspaceRoot, "company/mission.md"),
612
+ readOptional(workspaceRoot, "company/strategy.md"),
613
+ readOptional(workspaceRoot, "company/decisions.md"),
614
+ readEvents(workspaceRoot),
615
+ listTasks(workspaceRoot)
616
+ ]);
617
+ const relevantTasks = tasks.filter(
618
+ (task) => task.task.owner === agentDoc.agent.id || task.task.mentions.includes(agentDoc.agent.id)
619
+ );
620
+ return `You are ${agentDoc.agent.name} (${agentDoc.agent.role}) in Proactive AI.
621
+
622
+ ${agentDoc.instructions}
623
+
624
+ # Mission
625
+ ${mission}
626
+
627
+ # Strategy
628
+ ${strategy}
629
+
630
+ # Decisions
631
+ ${decisions}
632
+
633
+ # Recent Events
634
+ ${events.slice(-10).map((event) => `- ${event.id} ${event.type}: ${event.summary}`).join("\n")}
635
+
636
+ # Relevant Tasks
637
+ ${relevantTasks.map((task) => `- ${task.task.id} [${task.task.status}] ${task.task.title}`).join(
638
+ "\n"
639
+ )}
640
+
641
+ # Output Contract
642
+ Return ONLY JSON matching this shape:
643
+ {
644
+ "thought_summary": "brief reason",
645
+ "actions": [
646
+ { "type": "create_task", "title": "...", "priority": "medium", "owner": "agent-id", "content": "markdown body", "mentions": [] }
647
+ ]
648
+ }
649
+
650
+ Allowed action types and required fields:
651
+ - create_task: { "type": "create_task", "title": "...", "priority": "low|medium|high|critical", "owner": "agent-id", "content": "markdown body", "mentions": [] }
652
+ - create_artifact: { "type": "create_artifact", "path": "artifacts/...", "content": "markdown body" }
653
+ - create_inbox_item: { "type": "create_inbox_item", "kind": "decision_request", "title": "...", "priority": "low|medium|high|critical", "content": "markdown body" }
654
+ - comment: { "type": "comment", "path": "target/file.md", "content": "markdown body" }
655
+
656
+ For create_inbox_item, kind MUST be exactly "decision_request" or "approval_request". Do not use "decision".
657
+ Produce at most 2 actions. If no action is needed, return an empty actions array.`;
658
+ }
659
+
660
+ // src/agents/dedup.ts
661
+ function actionFingerprint(action) {
662
+ if (action.type === "create_task")
663
+ return `create_task:${normalize(action.title)}`;
664
+ if (action.type === "create_artifact")
665
+ return `create_artifact:${normalizePath(action.path)}`;
666
+ if (action.type === "create_inbox_item")
667
+ return `create_inbox_item:${action.kind}:${normalize(action.title)}`;
668
+ return `comment:${normalizePath(action.path)}:${normalize(action.content).slice(0, 80)}`;
669
+ }
670
+ function isDuplicateAction(action, existingTasks, existingInboxItems = []) {
671
+ if (action.type === "create_task") {
672
+ const title = normalize(action.title);
673
+ return existingTasks.some(
674
+ (task) => !["done", "cancelled"].includes(task.task.status) && normalize(task.task.title) === title
675
+ );
676
+ }
677
+ if (action.type === "create_inbox_item") {
678
+ const title = normalize(action.title);
679
+ return existingInboxItems.some(
680
+ (item) => item.item.status === "awaiting_human" && normalize(firstHeading(item.content)) === title
681
+ );
682
+ }
683
+ return false;
684
+ }
685
+ function firstHeading(content) {
686
+ return content.split("\n").find((line) => line.startsWith("# "))?.replace(/^# /, "") ?? "";
687
+ }
688
+ function normalize(value) {
689
+ return value.trim().toLowerCase().replace(/\s+/g, " ");
690
+ }
691
+ function normalizePath(value) {
692
+ return value.trim().replace(/^\/+/, "").replace(/\/+/g, "/");
693
+ }
694
+
695
+ // src/agents/permissions.ts
696
+ import path9 from "path";
697
+ import { minimatch } from "minimatch";
698
+ function canEditPath(agent, targetPath) {
699
+ return matchesAny(agent.can_edit, targetPath);
700
+ }
701
+ function canCommentPath(agent, targetPath) {
702
+ return matchesAny(agent.can_comment, targetPath);
703
+ }
704
+ function matchesAny(patterns, targetPath) {
705
+ const normalized = `/${targetPath.replace(/^\/+/, "")}`;
706
+ return patterns.some((pattern) => minimatch(normalized, pattern));
707
+ }
708
+ function assertSafeRelativePath(targetPath) {
709
+ if (path9.isAbsolute(targetPath) || targetPath.includes("..")) {
710
+ throw new Error(`Unsafe path: ${targetPath}`);
711
+ }
712
+ }
713
+
714
+ // src/agents/runner.ts
715
+ async function runAgent(workspaceRoot, agentDoc, provider, options = {}) {
716
+ const prompt = await buildAgentPrompt(workspaceRoot, agentDoc);
717
+ const response = await provider.complete({
718
+ agentId: agentDoc.agent.id,
719
+ prompt
720
+ });
721
+ const output = parseAgentOutput(response.text);
722
+ let actionCount = 0;
723
+ let skippedCount = 0;
724
+ const previews = [];
725
+ const fingerprints = options.actionFingerprints ?? /* @__PURE__ */ new Set();
726
+ for (const action of output.actions.slice(0, 2)) {
727
+ const fingerprint = actionFingerprint(action);
728
+ const [existingTasks, existingInboxItems] = await Promise.all([
729
+ listTasks(workspaceRoot),
730
+ listInboxItems(workspaceRoot)
731
+ ]);
732
+ if (fingerprints.has(fingerprint) || isDuplicateAction(action, existingTasks, existingInboxItems)) {
733
+ skippedCount += 1;
734
+ previews.push(`skip duplicate ${fingerprint}`);
735
+ continue;
736
+ }
737
+ fingerprints.add(fingerprint);
738
+ if (options.dryRun) {
739
+ previews.push(`would apply ${fingerprint}`);
740
+ actionCount += 1;
741
+ continue;
742
+ }
743
+ const applied = await applyAction(workspaceRoot, agentDoc, action);
744
+ previews.push(
745
+ `applied ${fingerprint} -> ${applied.join(", ") || "no files"}`
746
+ );
747
+ actionCount += 1;
748
+ }
749
+ return { agentId: agentDoc.agent.id, actionCount, skippedCount, previews };
750
+ }
751
+ async function applyAction(workspaceRoot, agentDoc, action) {
752
+ const state = await readState(workspaceRoot);
753
+ const now = (/* @__PURE__ */ new Date()).toISOString();
754
+ if (action.type === "create_task") {
755
+ const task = {
756
+ id: allocateId(state, "task", "task"),
757
+ title: action.title,
758
+ status: "backlog",
759
+ priority: action.priority,
760
+ owner: action.owner,
761
+ created_by: agentDoc.agent.id,
762
+ created_at: now,
763
+ updated_at: now,
764
+ mentions: action.mentions,
765
+ depends_on: [],
766
+ artifacts: [],
767
+ approval_required: false
768
+ };
769
+ const written = await writeTask(workspaceRoot, task, action.content);
770
+ state.last_event_id = formatEventId(state.next_ids.event);
771
+ await record(
772
+ workspaceRoot,
773
+ state.next_ids.event++,
774
+ "task.created",
775
+ agentDoc.agent.id,
776
+ `Created ${task.id}: ${task.title}`,
777
+ [written]
778
+ );
779
+ state.file_hashes[written] = await hashWorkspaceFile(
780
+ workspaceRoot,
781
+ written
782
+ );
783
+ await writeState(workspaceRoot, state);
784
+ return [written];
785
+ }
786
+ if (action.type === "create_artifact") {
787
+ const target = action.path.replace(/^\/+/, "");
788
+ assertSafeRelativePath(target);
789
+ if (!canEditPath(agentDoc.agent, target))
790
+ throw new Error(`${agentDoc.agent.id} cannot edit ${target}`);
791
+ if (await fileExists(path10.join(workspaceRoot, target)))
792
+ throw new Error(`Refusing to overwrite existing artifact: ${target}`);
793
+ await mkdir5(path10.dirname(path10.join(workspaceRoot, target)), {
794
+ recursive: true
795
+ });
796
+ await writeFile6(
797
+ path10.join(workspaceRoot, target),
798
+ action.content.endsWith("\n") ? action.content : `${action.content}
799
+ `
800
+ );
801
+ state.last_event_id = formatEventId(state.next_ids.event);
802
+ await record(
803
+ workspaceRoot,
804
+ state.next_ids.event++,
805
+ "artifact.created",
806
+ agentDoc.agent.id,
807
+ `Created ${target}`,
808
+ [target]
809
+ );
810
+ state.file_hashes[target] = await hashWorkspaceFile(workspaceRoot, target);
811
+ await writeState(workspaceRoot, state);
812
+ return [target];
813
+ }
814
+ if (action.type === "create_inbox_item") {
815
+ const id = allocateId(
816
+ state,
817
+ action.kind === "decision_request" ? "decision" : "approval",
818
+ action.kind === "decision_request" ? "decision" : "approval"
819
+ );
820
+ const item = {
821
+ id,
822
+ type: action.kind,
823
+ status: "awaiting_human",
824
+ priority: action.priority,
825
+ requested_by: agentDoc.agent.id,
826
+ related_task: action.related_task,
827
+ related_artifacts: [],
828
+ created_at: now
829
+ };
830
+ const written = await writeInboxItem(
831
+ workspaceRoot,
832
+ item,
833
+ `# ${action.title}
834
+
835
+ ${action.content}
836
+
837
+ ## CEO Response
838
+
839
+ <!-- Human writes here. -->
840
+ `
841
+ );
842
+ state.last_event_id = formatEventId(state.next_ids.event);
843
+ await record(
844
+ workspaceRoot,
845
+ state.next_ids.event++,
846
+ "approval.requested",
847
+ agentDoc.agent.id,
848
+ `Requested human input: ${action.title}`,
849
+ [written]
850
+ );
851
+ state.file_hashes[written] = await hashWorkspaceFile(
852
+ workspaceRoot,
853
+ written
854
+ );
855
+ await writeState(workspaceRoot, state);
856
+ return [written];
857
+ }
858
+ if (action.type === "comment") {
859
+ const target = action.path.replace(/^\/+/, "");
860
+ assertSafeRelativePath(target);
861
+ if (!canCommentPath(agentDoc.agent, target))
862
+ throw new Error(`${agentDoc.agent.id} cannot comment on ${target}`);
863
+ const commentPath = path10.join(
864
+ "comments",
865
+ `${Date.now()}-${agentDoc.agent.id}.md`
866
+ );
867
+ await mkdir5(path10.join(workspaceRoot, "comments"), { recursive: true });
868
+ await writeFile6(
869
+ path10.join(workspaceRoot, commentPath),
870
+ `# Comment on ${target}
871
+
872
+ ${action.content}
873
+ `
874
+ );
875
+ state.last_event_id = formatEventId(state.next_ids.event);
876
+ await record(
877
+ workspaceRoot,
878
+ state.next_ids.event++,
879
+ "comment.created",
880
+ agentDoc.agent.id,
881
+ `Commented on ${target}`,
882
+ [commentPath]
883
+ );
884
+ state.file_hashes[commentPath] = await hashWorkspaceFile(
885
+ workspaceRoot,
886
+ commentPath
887
+ );
888
+ await writeState(workspaceRoot, state);
889
+ return [commentPath];
890
+ }
891
+ return [];
892
+ }
893
+ async function fileExists(targetPath) {
894
+ try {
895
+ await access(targetPath);
896
+ return true;
897
+ } catch (error) {
898
+ if (error.code === "ENOENT") return false;
899
+ throw error;
900
+ }
901
+ }
902
+ async function record(workspaceRoot, eventNumber, type, actor, summary, files) {
903
+ const event = {
904
+ id: formatEventId(eventNumber),
905
+ type,
906
+ actor,
907
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
908
+ summary,
909
+ files
910
+ };
911
+ await appendEvent(workspaceRoot, event);
912
+ }
913
+
914
+ // src/providers/codex/codex-provider.ts
915
+ import { mkdtemp, readFile as readFile8, rm } from "fs/promises";
916
+ import os from "os";
917
+ import path11 from "path";
918
+ import { execa } from "execa";
919
+ var CodexProvider = class {
920
+ constructor(command = process.env.PROACTIVE_CODEX_COMMAND ?? "codex") {
921
+ this.command = command;
922
+ }
923
+ command;
924
+ async complete(request) {
925
+ const tempDir = await mkdtemp(path11.join(os.tmpdir(), "proactive-codex-"));
926
+ const outputPath = path11.join(tempDir, "last-message.txt");
927
+ try {
928
+ const result = await execa(
929
+ this.command,
930
+ [
931
+ "exec",
932
+ "--sandbox",
933
+ "read-only",
934
+ "--ignore-rules",
935
+ "--ephemeral",
936
+ "--output-last-message",
937
+ outputPath,
938
+ `${request.prompt}
939
+
940
+ Do not inspect files or run commands. Return only the JSON object requested above.`
941
+ ],
942
+ {
943
+ reject: false,
944
+ env: process.env,
945
+ stdin: "ignore",
946
+ timeout: Number.parseInt(
947
+ process.env.PROACTIVE_CODEX_TIMEOUT_MS ?? "120000",
948
+ 10
949
+ )
950
+ }
951
+ );
952
+ if (result.exitCode !== 0) {
953
+ throw new Error(
954
+ `Codex failed for ${request.agentId}: ${result.stderr || result.stdout}`
955
+ );
956
+ }
957
+ const text = await readFile8(outputPath, "utf8").catch(
958
+ () => result.stdout
959
+ );
960
+ if (!text.trim()) {
961
+ throw new Error(
962
+ `Codex produced no final message for ${request.agentId}: ${result.stderr || result.stdout}`
963
+ );
964
+ }
965
+ return { text };
966
+ } finally {
967
+ await rm(tempDir, { recursive: true, force: true });
968
+ }
969
+ }
970
+ };
971
+
972
+ // src/providers/stub/stub-provider.ts
973
+ var StubProvider = class {
974
+ constructor(scenario = "self-dogfood") {
975
+ this.scenario = scenario;
976
+ }
977
+ scenario;
978
+ async complete(request) {
979
+ const actions = this.scenario === "property-management" ? propertyManagementActions(request.agentId) : selfDogfoodActions();
980
+ return {
981
+ text: JSON.stringify({
982
+ thought_summary: `Stubbed ${this.scenario} response for ${request.agentId}.`,
983
+ actions
984
+ })
985
+ };
986
+ }
987
+ };
988
+ function selfDogfoodActions() {
989
+ return [
990
+ {
991
+ type: "create_task",
992
+ title: "Harden self-dogfood agent execution path",
993
+ priority: "high",
994
+ owner: "engineer-agent",
995
+ content: "## Context\n\nA stubbed Proactive AI run created this task while dogfooding the repo.\n\n## Request\n\nHarden the agent execution path before relying on live Codex runs.\n\n## Acceptance Criteria\n\n- [ ] Add provider contract tests.\n- [ ] Add dry-run/action preview mode.\n- [ ] Improve event id/state consistency.\n- [ ] Document safe live Codex invocation.\n",
996
+ mentions: ["engineer-agent"]
997
+ }
998
+ ];
999
+ }
1000
+ function propertyManagementActions(agentId) {
1001
+ if (agentId === "strategy-agent") {
1002
+ return [
1003
+ {
1004
+ type: "create_task",
1005
+ title: "Define initial property-management target customer",
1006
+ priority: "high",
1007
+ owner: "strategy-agent",
1008
+ content: "## Context\n\nThe CEO wants to build property-management software for landlords, property managers, and tenants.\n\n## Request\n\nCompare independent landlords, property managers, and tenants as initial ICPs. Recommend one starting segment.\n\n## Acceptance Criteria\n\n- [ ] Identifies the strongest initial ICP.\n- [ ] Lists assumptions and risks.\n- [ ] Recommends what to validate first.\n",
1009
+ mentions: ["pm-agent"]
1010
+ }
1011
+ ];
1012
+ }
1013
+ if (agentId === "pm-agent") {
1014
+ return [
1015
+ {
1016
+ type: "create_task",
1017
+ title: "Draft MVP PRD for property-management SaaS",
1018
+ priority: "high",
1019
+ owner: "pm-agent",
1020
+ content: "## Context\n\nThe product needs a narrow MVP scope.\n\n## Request\n\nDraft an MVP PRD focused on the selected initial customer segment. Include personas, workflows, non-goals, and open CEO decisions.\n\n## Acceptance Criteria\n\n- [ ] Defines MVP user and problem.\n- [ ] Lists core workflows.\n- [ ] Names non-goals.\n- [ ] Tags engineering for feasibility.\n",
1021
+ mentions: ["engineer-agent"]
1022
+ }
1023
+ ];
1024
+ }
1025
+ if (agentId === "engineer-agent") {
1026
+ return [
1027
+ {
1028
+ type: "create_artifact",
1029
+ path: "artifacts/engineering/property-management-feasibility.md",
1030
+ content: "# Property Management MVP Feasibility\n\n## Initial Read\n\nA simple v0 can avoid payment processing and focus on tenant directory, property/unit records, lease reminders, and maintenance requests.\n\n## Risks\n\n- Rent collection introduces payment/compliance complexity.\n- Multi-role permissions can expand scope quickly.\n\n## Recommendation\n\nDefer rent collection until after workflow validation.\n"
1031
+ }
1032
+ ];
1033
+ }
1034
+ return [
1035
+ {
1036
+ type: "create_inbox_item",
1037
+ kind: "decision_request",
1038
+ title: "Choose property-management MVP scope",
1039
+ priority: "high",
1040
+ content: "## Context\n\nAgents recommend starting with independent landlords and deferring rent collection.\n\n## Recommendation\n\nApprove an MVP focused on tenant directory, property/unit records, lease reminders, and maintenance requests.\n\n## CEO Response\n\n<!-- Approve, reject, or adjust scope. -->\n"
1041
+ }
1042
+ ];
1043
+ }
1044
+
1045
+ // src/cli/index.ts
1046
+ var program = new Command();
1047
+ program.name("proactive").description("Git-native proactive AI workspace CLI").version("0.1.0");
1048
+ program.command("init").argument("<workspace-name>").option("--root <path>", "workspace root; defaults to ./<workspace-name>").action(async (workspaceName, options) => {
1049
+ const root = path12.resolve(options.root ?? workspaceName);
1050
+ await initializeWorkspace(root, workspaceName);
1051
+ console.log(`Initialized Proactive AI workspace at ${root}`);
1052
+ });
1053
+ program.command("ceo").argument("<instruction>").action(async (instruction) => {
1054
+ const root = resolveWorkspace();
1055
+ const state = await readState(root);
1056
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1057
+ await mkdir6(path12.join(root, "company"), { recursive: true });
1058
+ await writeFile7(
1059
+ path12.join(root, "company", "latest-ceo-instruction.md"),
1060
+ `# CEO Instruction
1061
+
1062
+ ${instruction}
1063
+ `
1064
+ );
1065
+ const eventId = formatEventId(state.next_ids.event++);
1066
+ await appendEvent(root, {
1067
+ id: eventId,
1068
+ type: "ceo.instruction.created",
1069
+ actor: "human-ceo",
1070
+ timestamp: now,
1071
+ summary: instruction
1072
+ });
1073
+ state.last_event_id = eventId;
1074
+ await writeState(root, state);
1075
+ console.log(`Recorded CEO instruction: ${instruction}`);
1076
+ });
1077
+ program.command("tasks").action(async () => {
1078
+ const tasks = await listTasks(resolveWorkspace());
1079
+ for (const doc of tasks)
1080
+ console.log(
1081
+ `${doc.task.id} ${doc.task.status} ${doc.task.priority} ${doc.task.title}`
1082
+ );
1083
+ });
1084
+ program.command("inbox").action(async () => {
1085
+ const items = (await listInboxItems(resolveWorkspace())).filter(
1086
+ (doc) => doc.item.status === "awaiting_human"
1087
+ );
1088
+ if (items.length === 0) {
1089
+ console.log("Inbox is empty.");
1090
+ return;
1091
+ }
1092
+ for (const doc of items)
1093
+ console.log(
1094
+ `${doc.item.id} ${doc.item.priority} ${doc.item.type} ${doc.path}`
1095
+ );
1096
+ });
1097
+ async function respond(itemId, status, response) {
1098
+ const root = resolveWorkspace();
1099
+ const doc = await findInboxItem(root, itemId);
1100
+ if (!doc) throw new Error(`Inbox item not found: ${itemId}`);
1101
+ await respondToInboxItem(root, doc, status, response);
1102
+ if (doc.item.type === "decision_request" && status === "approved") {
1103
+ const decisionsPath = path12.join(root, "company", "decisions.md");
1104
+ const existing = await readFile9(decisionsPath, "utf8").catch(
1105
+ () => "# Decisions\n\n"
1106
+ );
1107
+ await writeFile7(
1108
+ decisionsPath,
1109
+ `${existing.trimEnd()}
1110
+
1111
+ ## ${(/* @__PURE__ */ new Date()).toISOString()}: ${itemId}
1112
+
1113
+ ${response}
1114
+ `
1115
+ );
1116
+ }
1117
+ const state = await readState(root);
1118
+ const eventId = formatEventId(state.next_ids.event++);
1119
+ await appendEvent(root, {
1120
+ id: eventId,
1121
+ type: status === "approved" ? "approval.approved" : "approval.rejected",
1122
+ actor: "human-ceo",
1123
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1124
+ summary: `${status} ${itemId}: ${response}`
1125
+ });
1126
+ state.last_event_id = eventId;
1127
+ await writeState(root, state);
1128
+ console.log(`${status} ${itemId}`);
1129
+ }
1130
+ program.command("approve").argument("<item-id>").requiredOption("--response <text>").action(
1131
+ async (itemId, options) => respond(itemId, "approved", options.response)
1132
+ );
1133
+ program.command("reject").argument("<item-id>").requiredOption("--response <text>").action(
1134
+ async (itemId, options) => respond(itemId, "rejected", options.response)
1135
+ );
1136
+ program.command("agents").action(async () => {
1137
+ const agents = await listAgents(resolveWorkspace());
1138
+ for (const doc of agents)
1139
+ console.log(`${doc.agent.id} ${doc.agent.status} ${doc.agent.role}`);
1140
+ });
1141
+ program.command("run").option("--no-ai", "detect changes and plan wakeups without invoking Codex").option(
1142
+ "--dry-run",
1143
+ "call the provider and preview actions without writing files"
1144
+ ).option("--provider <provider>", "AI provider to use: codex or stub", "codex").option(
1145
+ "--scenario <scenario>",
1146
+ "stub scenario to run: self-dogfood or property-management",
1147
+ "self-dogfood"
1148
+ ).option(
1149
+ "--max-agents <count>",
1150
+ "maximum agents to run when AI is enabled",
1151
+ "1"
1152
+ ).action(
1153
+ async (options) => {
1154
+ const root = resolveWorkspace();
1155
+ const changes = await detectWorkspaceChanges(root);
1156
+ const [events, agents, tasks] = await Promise.all([
1157
+ readEvents(root),
1158
+ listAgents(root),
1159
+ listTasks(root)
1160
+ ]);
1161
+ const wakeups = planAgentWakeups(agents, events, tasks);
1162
+ console.log(
1163
+ `Detected ${changes} change(s). Wakeups: ${wakeups.map((doc) => doc.agent.id).join(", ") || "none"}`
1164
+ );
1165
+ if (!options.ai) return;
1166
+ const provider = options.provider === "stub" ? new StubProvider(options.scenario) : new CodexProvider();
1167
+ const maxAgents = Number.parseInt(options.maxAgents, 10);
1168
+ const selectedWakeups = wakeups.slice(
1169
+ 0,
1170
+ Number.isFinite(maxAgents) && maxAgents > 0 ? maxAgents : 1
1171
+ );
1172
+ const actionFingerprints = /* @__PURE__ */ new Set();
1173
+ for (const agent of selectedWakeups) {
1174
+ const result = await runAgent(root, agent, provider, {
1175
+ dryRun: options.dryRun,
1176
+ actionFingerprints
1177
+ });
1178
+ console.log(
1179
+ `${options.dryRun ? "Previewed" : "Ran"} ${result.agentId}: ${result.actionCount} action(s), ${result.skippedCount} skipped`
1180
+ );
1181
+ for (const preview of result.previews) console.log(` - ${preview}`);
1182
+ }
1183
+ }
1184
+ );
1185
+ program.parseAsync().catch((error) => {
1186
+ console.error(error instanceof Error ? error.message : String(error));
1187
+ process.exitCode = 1;
1188
+ });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@trycedar/argus",
3
+ "version": "0.1.0",
4
+ "description": "Git-native proactive AI agent organization framework CLI.",
5
+ "type": "module",
6
+ "packageManager": "pnpm@10.25.0",
7
+ "bin": {
8
+ "proactive": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "CHANGELOG.md"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsx src/cli/index.ts",
21
+ "format:check": "prettier --check .",
22
+ "format": "prettier --write .",
23
+ "lint": "tsc --noEmit",
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "vitest run"
26
+ },
27
+ "dependencies": {
28
+ "commander": "^14.0.2",
29
+ "execa": "^9.6.0",
30
+ "fast-glob": "^3.3.3",
31
+ "gray-matter": "^4.0.3",
32
+ "minimatch": "^10.1.1",
33
+ "yaml": "^2.8.1",
34
+ "zod": "^4.1.13"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^24.10.1",
38
+ "prettier": "^3.6.2",
39
+ "tsup": "^8.5.1",
40
+ "tsx": "^4.20.6",
41
+ "typescript": "^5.9.3",
42
+ "vitest": "^4.0.14"
43
+ }
44
+ }