@poa-box/agent 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.
Files changed (139) hide show
  1. package/.env.agent.template +20 -0
  2. package/README.md +46 -0
  3. package/brain/Config/agent-config.json +14 -0
  4. package/brain/Config/brain-allowlist.json +20 -0
  5. package/brain/Identity/goals.template.md +23 -0
  6. package/brain/Identity/how-i-think.md +406 -0
  7. package/brain/Identity/who-i-am.template.md +34 -0
  8. package/brain/Knowledge/BOOTSTRAP.md +66 -0
  9. package/brain/Knowledge/audit-corpus-index.json +406 -0
  10. package/brain/Knowledge/discussions.json +245 -0
  11. package/brain/Knowledge/pop.brain.brainstorms.generated.md +48 -0
  12. package/brain/Knowledge/pop.brain.brainstorms.genesis.bin +0 -0
  13. package/brain/Knowledge/pop.brain.heuristics.snapshot.bin +0 -0
  14. package/brain/Knowledge/pop.brain.projects.generated.md +16 -0
  15. package/brain/Knowledge/pop.brain.projects.genesis.bin +0 -0
  16. package/brain/Knowledge/pop.brain.retros.generated.md +91 -0
  17. package/brain/Knowledge/pop.brain.retros.genesis.bin +0 -0
  18. package/brain/Knowledge/pop.brain.shared.generated.md +3811 -0
  19. package/brain/Knowledge/pop.brain.shared.genesis.bin +0 -0
  20. package/brain/Knowledge/projects.md +181 -0
  21. package/brain/Knowledge/risk-framework.md +90 -0
  22. package/brain/Knowledge/shared.md +416 -0
  23. package/brain/Knowledge/sprint-priorities.md +439 -0
  24. package/brain/Memory/.gitkeep +0 -0
  25. package/dist/commands/agent/daily-digest.d.ts +24 -0
  26. package/dist/commands/agent/daily-digest.js +336 -0
  27. package/dist/commands/agent/delegate.d.ts +12 -0
  28. package/dist/commands/agent/delegate.js +91 -0
  29. package/dist/commands/agent/deploy-to-org.d.ts +20 -0
  30. package/dist/commands/agent/deploy-to-org.js +154 -0
  31. package/dist/commands/agent/index.d.ts +2 -0
  32. package/dist/commands/agent/index.js +27 -0
  33. package/dist/commands/agent/init.d.ts +19 -0
  34. package/dist/commands/agent/init.js +303 -0
  35. package/dist/commands/agent/onboard.d.ts +22 -0
  36. package/dist/commands/agent/onboard.js +192 -0
  37. package/dist/commands/agent/paymaster-status.d.ts +14 -0
  38. package/dist/commands/agent/paymaster-status.js +130 -0
  39. package/dist/commands/agent/register.d.ts +21 -0
  40. package/dist/commands/agent/register.js +116 -0
  41. package/dist/commands/agent/setup-sponsorship.d.ts +22 -0
  42. package/dist/commands/agent/setup-sponsorship.js +154 -0
  43. package/dist/commands/agent/status.d.ts +12 -0
  44. package/dist/commands/agent/status.js +171 -0
  45. package/dist/commands/agent/triage.d.ts +12 -0
  46. package/dist/commands/agent/triage.js +503 -0
  47. package/dist/commands/brain/advance-stage.d.ts +42 -0
  48. package/dist/commands/brain/advance-stage.js +206 -0
  49. package/dist/commands/brain/allowlist.d.ts +30 -0
  50. package/dist/commands/brain/allowlist.js +274 -0
  51. package/dist/commands/brain/append-lesson.d.ts +55 -0
  52. package/dist/commands/brain/append-lesson.js +245 -0
  53. package/dist/commands/brain/brainstorm.d.ts +154 -0
  54. package/dist/commands/brain/brainstorm.js +573 -0
  55. package/dist/commands/brain/daemon.d.ts +31 -0
  56. package/dist/commands/brain/daemon.js +348 -0
  57. package/dist/commands/brain/doctor.d.ts +27 -0
  58. package/dist/commands/brain/doctor.js +497 -0
  59. package/dist/commands/brain/edit-lesson.d.ts +51 -0
  60. package/dist/commands/brain/edit-lesson.js +248 -0
  61. package/dist/commands/brain/import-snapshot.d.ts +68 -0
  62. package/dist/commands/brain/import-snapshot.js +177 -0
  63. package/dist/commands/brain/index.d.ts +2 -0
  64. package/dist/commands/brain/index.js +67 -0
  65. package/dist/commands/brain/list.d.ts +21 -0
  66. package/dist/commands/brain/list.js +83 -0
  67. package/dist/commands/brain/migrate-projects.d.ts +44 -0
  68. package/dist/commands/brain/migrate-projects.js +209 -0
  69. package/dist/commands/brain/migrate.d.ts +74 -0
  70. package/dist/commands/brain/migrate.js +306 -0
  71. package/dist/commands/brain/new-project.d.ts +53 -0
  72. package/dist/commands/brain/new-project.js +226 -0
  73. package/dist/commands/brain/read.d.ts +24 -0
  74. package/dist/commands/brain/read.js +81 -0
  75. package/dist/commands/brain/remove-lesson.d.ts +47 -0
  76. package/dist/commands/brain/remove-lesson.js +206 -0
  77. package/dist/commands/brain/remove-project.d.ts +36 -0
  78. package/dist/commands/brain/remove-project.js +177 -0
  79. package/dist/commands/brain/retro-file-tasks.d.ts +84 -0
  80. package/dist/commands/brain/retro-file-tasks.js +372 -0
  81. package/dist/commands/brain/retro-list.d.ts +28 -0
  82. package/dist/commands/brain/retro-list.js +125 -0
  83. package/dist/commands/brain/retro-mark-change.d.ts +58 -0
  84. package/dist/commands/brain/retro-mark-change.js +176 -0
  85. package/dist/commands/brain/retro-remove.d.ts +36 -0
  86. package/dist/commands/brain/retro-remove.js +142 -0
  87. package/dist/commands/brain/retro-respond.d.ts +56 -0
  88. package/dist/commands/brain/retro-respond.js +250 -0
  89. package/dist/commands/brain/retro-show.d.ts +23 -0
  90. package/dist/commands/brain/retro-show.js +100 -0
  91. package/dist/commands/brain/retro-start.d.ts +55 -0
  92. package/dist/commands/brain/retro-start.js +311 -0
  93. package/dist/commands/brain/search.d.ts +48 -0
  94. package/dist/commands/brain/search.js +190 -0
  95. package/dist/commands/brain/snapshot.d.ts +32 -0
  96. package/dist/commands/brain/snapshot.js +243 -0
  97. package/dist/commands/brain/status.d.ts +15 -0
  98. package/dist/commands/brain/status.js +166 -0
  99. package/dist/commands/brain/subscribe.d.ts +28 -0
  100. package/dist/commands/brain/subscribe.js +90 -0
  101. package/dist/commands/brain/tag.d.ts +46 -0
  102. package/dist/commands/brain/tag.js +192 -0
  103. package/dist/index.d.ts +17 -0
  104. package/dist/index.js +22 -0
  105. package/dist/lib/brain-daemon.d.ts +126 -0
  106. package/dist/lib/brain-daemon.js +811 -0
  107. package/dist/lib/brain-membership.d.ts +58 -0
  108. package/dist/lib/brain-membership.js +115 -0
  109. package/dist/lib/brain-migrate-projects.d.ts +43 -0
  110. package/dist/lib/brain-migrate-projects.js +247 -0
  111. package/dist/lib/brain-migrate.d.ts +77 -0
  112. package/dist/lib/brain-migrate.js +328 -0
  113. package/dist/lib/brain-ops.d.ts +271 -0
  114. package/dist/lib/brain-ops.js +571 -0
  115. package/dist/lib/brain-paths.d.ts +15 -0
  116. package/dist/lib/brain-paths.js +33 -0
  117. package/dist/lib/brain-projections.d.ts +216 -0
  118. package/dist/lib/brain-projections.js +829 -0
  119. package/dist/lib/brain-schemas.d.ts +36 -0
  120. package/dist/lib/brain-schemas.js +316 -0
  121. package/dist/lib/brain-signing.d.ts +103 -0
  122. package/dist/lib/brain-signing.js +256 -0
  123. package/dist/lib/brain.d.ts +198 -0
  124. package/dist/lib/brain.js +1057 -0
  125. package/dist/pop-agent.d.ts +1 -0
  126. package/dist/pop-agent.js +18 -0
  127. package/docs/agent.md +126 -0
  128. package/docs/agents/brain-anti-entropy.md +127 -0
  129. package/docs/agents/brain-cross-device-onboarding.md +210 -0
  130. package/docs/agents/brain-cross-machine-smoke.md +241 -0
  131. package/docs/agents/brain-layer-setup.md +725 -0
  132. package/docs/agents/offboarding-protocol.md +188 -0
  133. package/docs/agents/onboarding-protocol.md +243 -0
  134. package/docs/agents/running-an-agent.md +200 -0
  135. package/docs/brain.md +560 -0
  136. package/package.json +61 -0
  137. package/scripts/apply.sh +140 -0
  138. package/scripts/onboard.sh +205 -0
  139. package/scripts/setup-agent.ts +272 -0
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ /**
3
+ * pop brain migrate — one-shot import of a hand-written brain knowledge
4
+ * file into a CRDT Automerge doc. Step 8 of the brain plan.
5
+ *
6
+ * Usage:
7
+ * pop brain migrate --from agent/brain/Knowledge/shared.md \
8
+ * --doc pop.brain.shared
9
+ *
10
+ * Flow:
11
+ * 1. Read the markdown file from disk.
12
+ * 2. parseSharedMarkdown → SharedBrainDoc (rules + lessons).
13
+ * 3. --dry-run: print the parsed structure and exit.
14
+ * 4. Otherwise: check that the target doc is empty (or --force),
15
+ * then apply the parsed content via applyBrainChange. This
16
+ * auto-signs + auto-publishes via the step 5 gossipsub layer
17
+ * so any live subscriber picks up the migration immediately.
18
+ * 5. Write a DEPRECATED banner to the top of the source file
19
+ * pointing agents at <docId>.generated.md as the new source
20
+ * of truth. Original content is preserved below the banner
21
+ * for historical reference — the banner is opt-in.
22
+ *
23
+ * Idempotence: running the command twice on a doc that already has
24
+ * content errors out unless --force is supplied. This protects
25
+ * against accidental clobbering during a fumbled migration.
26
+ */
27
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ var desc = Object.getOwnPropertyDescriptor(m, k);
30
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
31
+ desc = { enumerable: true, get: function() { return m[k]; } };
32
+ }
33
+ Object.defineProperty(o, k2, desc);
34
+ }) : (function(o, m, k, k2) {
35
+ if (k2 === undefined) k2 = k;
36
+ o[k2] = m[k];
37
+ }));
38
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
39
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
40
+ }) : function(o, v) {
41
+ o["default"] = v;
42
+ });
43
+ var __importStar = (this && this.__importStar) || (function () {
44
+ var ownKeys = function(o) {
45
+ ownKeys = Object.getOwnPropertyNames || function (o) {
46
+ var ar = [];
47
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
48
+ return ar;
49
+ };
50
+ return ownKeys(o);
51
+ };
52
+ return function (mod) {
53
+ if (mod && mod.__esModule) return mod;
54
+ var result = {};
55
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
56
+ __setModuleDefault(result, mod);
57
+ return result;
58
+ };
59
+ })();
60
+ Object.defineProperty(exports, "__esModule", { value: true });
61
+ exports.migrateHandler = void 0;
62
+ exports.computeMergeDelta = computeMergeDelta;
63
+ const fs_1 = require("fs");
64
+ const path_1 = require("path");
65
+ const brain_1 = require("../../lib/brain");
66
+ const brain_migrate_1 = require("../../lib/brain-migrate");
67
+ const output = __importStar(require("@poa-box/cli/lib/output"));
68
+ /**
69
+ * Banner injected at the top of the source hand-written file after a
70
+ * successful migration. Keeps the original content below so git
71
+ * history + human review are preserved; future hand-edits still
72
+ * survive but are flagged as out-of-sync with the CRDT.
73
+ */
74
+ const DEPRECATED_BANNER = (docId, headCid) => `<!-- DEPRECATED: migrated to ${docId} at CID ${headCid} -->
75
+ <!-- Source of truth is now agent/brain/Knowledge/${docId}.generated.md -->
76
+ <!-- regenerated by \`pop brain snapshot --doc ${docId}\` at end of every heartbeat. -->
77
+ <!-- Edit via \`pop brain\` CLI; hand-edits here will NOT propagate. -->
78
+
79
+ `;
80
+ /**
81
+ * Pure helper: compute which parsed entries should be appended to an
82
+ * existing list vs skipped because an entry with the same `id` already
83
+ * exists. Extracted so the --merge dedup logic is unit-testable
84
+ * independent of the brain daemon / Automerge doc layer.
85
+ *
86
+ * Entries without an `id` are ALWAYS added (we can't dedup what has no
87
+ * key). Callers should ensure parsed entries have ids if they want
88
+ * dedup semantics — parseSharedMarkdown always assigns an id via
89
+ * slugify fallback, so in practice every parsed lesson has an id.
90
+ */
91
+ function computeMergeDelta(existing, parsed) {
92
+ const existingIds = new Set(existing
93
+ .map((e) => e?.id)
94
+ .filter((id) => typeof id === 'string'));
95
+ const toAdd = [];
96
+ let skippedCount = 0;
97
+ for (const entry of parsed) {
98
+ if (entry.id && existingIds.has(entry.id)) {
99
+ skippedCount += 1;
100
+ continue;
101
+ }
102
+ toAdd.push(entry);
103
+ }
104
+ return { toAdd, skippedCount };
105
+ }
106
+ exports.migrateHandler = {
107
+ builder: (yargs) => yargs
108
+ .option('from', {
109
+ describe: 'Path to the hand-written markdown file to migrate',
110
+ type: 'string',
111
+ demandOption: true,
112
+ })
113
+ .option('doc', {
114
+ describe: 'Target brain document ID (e.g. pop.brain.shared)',
115
+ type: 'string',
116
+ demandOption: true,
117
+ })
118
+ .option('force', {
119
+ describe: 'Re-migrate even if the target doc already has content (DESTRUCTIVE: clears existing lessons/rules before re-seeding)',
120
+ type: 'boolean',
121
+ default: false,
122
+ })
123
+ .option('merge', {
124
+ describe: 'Non-destructive append: migrate new entries into an existing doc, deduping by id (mutually exclusive with --force)',
125
+ type: 'boolean',
126
+ default: false,
127
+ })
128
+ .option('dry-run', {
129
+ describe: 'Parse and print the structured output without persisting',
130
+ type: 'boolean',
131
+ default: false,
132
+ })
133
+ .option('current-hb', {
134
+ describe: 'Current heartbeat number for HB#→timestamp estimation (default 270)',
135
+ type: 'number',
136
+ default: 270,
137
+ })
138
+ .option('author', {
139
+ describe: 'Default author for sections without explicit attribution',
140
+ type: 'string',
141
+ default: 'migration',
142
+ })
143
+ .option('no-banner', {
144
+ describe: 'Do NOT write a DEPRECATED banner to the source file after migration',
145
+ type: 'boolean',
146
+ default: false,
147
+ }),
148
+ handler: async (argv) => {
149
+ const srcPath = (0, path_1.resolve)(argv.from);
150
+ const docId = argv.doc;
151
+ try {
152
+ if (argv.force && argv.merge) {
153
+ output.error('--force and --merge are mutually exclusive. --force clears and re-seeds; ' +
154
+ '--merge appends dedup-by-id into existing content. Pick one.');
155
+ process.exitCode = 1;
156
+ return;
157
+ }
158
+ if (!(0, fs_1.existsSync)(srcPath)) {
159
+ output.error(`Source file not found: ${srcPath}`);
160
+ process.exitCode = 1;
161
+ return;
162
+ }
163
+ const raw = (0, fs_1.readFileSync)(srcPath, 'utf8');
164
+ const now = Math.floor(Date.now() / 1000);
165
+ const currentHB = argv.currentHb ?? 270;
166
+ const ctx = {
167
+ defaultAuthor: argv.author ?? 'migration',
168
+ defaultTimestamp: now,
169
+ timestampForHB: (hb) => now - (currentHB - hb) * 15 * 60,
170
+ };
171
+ const parsed = (0, brain_migrate_1.parseSharedMarkdown)(raw, ctx);
172
+ if (argv.dryRun) {
173
+ if (output.isJsonMode()) {
174
+ output.json({ status: 'dry-run', docId, parsed });
175
+ }
176
+ else {
177
+ console.log('');
178
+ console.log(` Dry-run migration: ${srcPath}`);
179
+ console.log(` Target doc: ${docId}`);
180
+ console.log(' ' + '─'.repeat(60));
181
+ console.log(` Rules: ${parsed.rules.length}`);
182
+ console.log(` Lessons: ${parsed.lessons.length}`);
183
+ console.log('');
184
+ console.log(' First 3 rules:');
185
+ for (const r of parsed.rules.slice(0, 3)) {
186
+ console.log(` - ${r.id}: ${(r.text ?? '').slice(0, 80).replace(/\n/g, ' ')}...`);
187
+ }
188
+ console.log(' First 3 lessons:');
189
+ for (const l of parsed.lessons.slice(0, 3)) {
190
+ console.log(` - ${l.id}: ${(l.title ?? '').slice(0, 80)}`);
191
+ }
192
+ console.log('');
193
+ console.log(' No changes persisted (--dry-run).');
194
+ console.log('');
195
+ }
196
+ return;
197
+ }
198
+ // Refuse to overwrite an existing doc unless --force or --merge.
199
+ const existing = await (0, brain_1.openBrainDoc)(docId);
200
+ const alreadyHasContent = existing.headCid !== null &&
201
+ existing.doc &&
202
+ (Object.keys(existing.doc).length > 0);
203
+ if (alreadyHasContent && !argv.force && !argv.merge) {
204
+ output.error(`Target doc "${docId}" already has content at head ${existing.headCid}. ` +
205
+ 'Re-run with --merge to append new entries non-destructively (dedup by id), ' +
206
+ 'or --force to clear-and-reseed (DESTRUCTIVE).');
207
+ process.exitCode = 1;
208
+ return;
209
+ }
210
+ // Merge-mode stats: set in the change callback, read after the apply.
211
+ let lessonsAdded = 0;
212
+ let lessonsSkipped = 0;
213
+ let rulesAdded = 0;
214
+ let rulesSkipped = 0;
215
+ // Apply the parsed state in a single Automerge change so the
216
+ // entire migration lands as one content-addressed snapshot.
217
+ const result = await (0, brain_1.applyBrainChange)(docId, (doc) => {
218
+ if (!doc.rules)
219
+ doc.rules = [];
220
+ if (!doc.lessons)
221
+ doc.lessons = [];
222
+ if (argv.force) {
223
+ // Force re-migration: clear existing and re-seed.
224
+ doc.rules.splice(0, doc.rules.length);
225
+ doc.lessons.splice(0, doc.lessons.length);
226
+ }
227
+ if (argv.merge) {
228
+ // Non-destructive append: dedup by id against existing entries.
229
+ const rulesDelta = computeMergeDelta(doc.rules, parsed.rules);
230
+ const lessonsDelta = computeMergeDelta(doc.lessons, parsed.lessons);
231
+ for (const r of rulesDelta.toAdd)
232
+ doc.rules.push({ ...r });
233
+ for (const l of lessonsDelta.toAdd)
234
+ doc.lessons.push({ ...l });
235
+ rulesAdded = rulesDelta.toAdd.length;
236
+ rulesSkipped = rulesDelta.skippedCount;
237
+ lessonsAdded = lessonsDelta.toAdd.length;
238
+ lessonsSkipped = lessonsDelta.skippedCount;
239
+ return;
240
+ }
241
+ // Legacy / --force path: append everything unconditionally.
242
+ for (const r of parsed.rules) {
243
+ doc.rules.push({ ...r });
244
+ rulesAdded += 1;
245
+ }
246
+ for (const l of parsed.lessons) {
247
+ doc.lessons.push({ ...l });
248
+ lessonsAdded += 1;
249
+ }
250
+ });
251
+ // Write the deprecated banner to the source file unless
252
+ // explicitly suppressed. Keep the original content below.
253
+ if (!argv.noBanner) {
254
+ const banner = DEPRECATED_BANNER(docId, result.headCid);
255
+ if (!raw.includes('DEPRECATED: migrated to')) {
256
+ (0, fs_1.writeFileSync)(srcPath, banner + raw);
257
+ }
258
+ }
259
+ if (output.isJsonMode()) {
260
+ output.json({
261
+ status: 'ok',
262
+ docId,
263
+ headCid: result.headCid,
264
+ mode: argv.merge ? 'merge' : argv.force ? 'force' : 'fresh',
265
+ rulesAdded,
266
+ rulesSkipped,
267
+ lessonsAdded,
268
+ lessonsSkipped,
269
+ // Back-compat keys — total parsed, not added.
270
+ rulesMigrated: parsed.rules.length,
271
+ lessonsMigrated: parsed.lessons.length,
272
+ sourceFile: srcPath,
273
+ bannerWritten: !argv.noBanner,
274
+ });
275
+ }
276
+ else {
277
+ console.log('');
278
+ console.log(` Migration complete: ${srcPath} → ${docId}`);
279
+ console.log(` Mode: ${argv.merge ? 'merge' : argv.force ? 'force' : 'fresh'}`);
280
+ console.log(` New head CID: ${result.headCid}`);
281
+ if (argv.merge) {
282
+ console.log(` Rules: +${rulesAdded} added, ${rulesSkipped} skipped (dup id)`);
283
+ console.log(` Lessons: +${lessonsAdded} added, ${lessonsSkipped} skipped (dup id)`);
284
+ }
285
+ else {
286
+ console.log(` Rules: ${rulesAdded}`);
287
+ console.log(` Lessons: ${lessonsAdded}`);
288
+ }
289
+ if (!argv.noBanner) {
290
+ console.log(` Banner written to source file.`);
291
+ }
292
+ console.log('');
293
+ console.log(` Run \`pop brain read --doc ${docId}\` to inspect.`);
294
+ console.log(` Run \`pop brain snapshot --doc ${docId}\` to produce the .generated.md.`);
295
+ console.log('');
296
+ }
297
+ }
298
+ catch (err) {
299
+ output.error(err.message);
300
+ process.exitCode = 1;
301
+ }
302
+ finally {
303
+ await (0, brain_1.stopBrainNode)();
304
+ }
305
+ },
306
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * pop brain new-project — create a project entry in pop.brain.projects.
3
+ *
4
+ * First write command for the pop.brain.projects doc shape introduced
5
+ * in #306. Thin wrapper over applyBrainChange that pushes a new
6
+ * BrainProject onto doc.projects and auto-publishes via the existing
7
+ * gossipsub hook. Validates stage against the ProjectStage union so
8
+ * typos get caught at the CLI boundary.
9
+ *
10
+ * Out of scope for this command (each is a separate follow-up):
11
+ * - advance-stage (advance a project to the next lifecycle stage)
12
+ * - post-discussion (append a discussion entry)
13
+ * - add-task-plan (attach a task plan block)
14
+ * - stage-transition validation (currently any stage is accepted as
15
+ * the starting stage; the PROPOSE → DISCUSS → PLAN order is a soft
16
+ * convention, not a hard constraint)
17
+ */
18
+ import type { Argv, ArgumentsCamelCase } from 'yargs';
19
+ import type { ProjectStage } from '../../lib/brain-projections';
20
+ interface NewProjectArgs {
21
+ doc: string;
22
+ name: string;
23
+ stage?: ProjectStage;
24
+ proposedBy?: string;
25
+ brief?: string;
26
+ briefFile?: string;
27
+ id?: string;
28
+ 'idempotency-key'?: string;
29
+ 'no-idempotency'?: boolean;
30
+ }
31
+ export declare const newProjectHandler: {
32
+ builder: (yargs: Argv) => Argv<{
33
+ doc: string;
34
+ } & {
35
+ name: string;
36
+ } & {
37
+ stage: ProjectStage;
38
+ } & {
39
+ "proposed-by": string | undefined;
40
+ } & {
41
+ brief: string | undefined;
42
+ } & {
43
+ "brief-file": string | undefined;
44
+ } & {
45
+ id: string | undefined;
46
+ } & {
47
+ "idempotency-key": string | undefined;
48
+ } & {
49
+ "no-idempotency": boolean;
50
+ }>;
51
+ handler: (argv: ArgumentsCamelCase<NewProjectArgs>) => Promise<void>;
52
+ };
53
+ export {};
@@ -0,0 +1,226 @@
1
+ "use strict";
2
+ /**
3
+ * pop brain new-project — create a project entry in pop.brain.projects.
4
+ *
5
+ * First write command for the pop.brain.projects doc shape introduced
6
+ * in #306. Thin wrapper over applyBrainChange that pushes a new
7
+ * BrainProject onto doc.projects and auto-publishes via the existing
8
+ * gossipsub hook. Validates stage against the ProjectStage union so
9
+ * typos get caught at the CLI boundary.
10
+ *
11
+ * Out of scope for this command (each is a separate follow-up):
12
+ * - advance-stage (advance a project to the next lifecycle stage)
13
+ * - post-discussion (append a discussion entry)
14
+ * - add-task-plan (attach a task plan block)
15
+ * - stage-transition validation (currently any stage is accepted as
16
+ * the starting stage; the PROPOSE → DISCUSS → PLAN order is a soft
17
+ * convention, not a hard constraint)
18
+ */
19
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
+ desc = { enumerable: true, get: function() { return m[k]; } };
24
+ }
25
+ Object.defineProperty(o, k2, desc);
26
+ }) : (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ o[k2] = m[k];
29
+ }));
30
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32
+ }) : function(o, v) {
33
+ o["default"] = v;
34
+ });
35
+ var __importStar = (this && this.__importStar) || (function () {
36
+ var ownKeys = function(o) {
37
+ ownKeys = Object.getOwnPropertyNames || function (o) {
38
+ var ar = [];
39
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
+ return ar;
41
+ };
42
+ return ownKeys(o);
43
+ };
44
+ return function (mod) {
45
+ if (mod && mod.__esModule) return mod;
46
+ var result = {};
47
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
+ __setModuleDefault(result, mod);
49
+ return result;
50
+ };
51
+ })();
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.newProjectHandler = void 0;
54
+ const fs_1 = require("fs");
55
+ const path_1 = require("path");
56
+ const ethers_1 = require("ethers");
57
+ const brain_1 = require("../../lib/brain");
58
+ const brain_ops_1 = require("../../lib/brain-ops");
59
+ const idempotency_1 = require("@poa-box/cli/lib/idempotency");
60
+ const output = __importStar(require("@poa-box/cli/lib/output"));
61
+ const VALID_STAGES = [
62
+ 'propose', 'discuss', 'plan', 'vote', 'execute', 'review', 'ship',
63
+ ];
64
+ function slugify(s) {
65
+ return s
66
+ .toLowerCase()
67
+ .replace(/[^a-z0-9]+/g, '-')
68
+ .replace(/^-+|-+$/g, '')
69
+ .slice(0, 60);
70
+ }
71
+ exports.newProjectHandler = {
72
+ builder: (yargs) => yargs
73
+ .option('doc', {
74
+ describe: 'Target brain document ID (typically pop.brain.projects)',
75
+ type: 'string',
76
+ demandOption: true,
77
+ })
78
+ .option('name', {
79
+ describe: 'Project name (used as the markdown header + default id base)',
80
+ type: 'string',
81
+ demandOption: true,
82
+ })
83
+ .option('stage', {
84
+ describe: 'Starting lifecycle stage',
85
+ type: 'string',
86
+ choices: VALID_STAGES,
87
+ default: 'propose',
88
+ })
89
+ .option('proposed-by', {
90
+ describe: 'Override the proposer label (default: signing wallet address)',
91
+ type: 'string',
92
+ })
93
+ .option('brief', {
94
+ describe: 'Inline project brief (short form)',
95
+ type: 'string',
96
+ })
97
+ .option('brief-file', {
98
+ describe: 'Path to a file whose contents are used as the brief',
99
+ type: 'string',
100
+ })
101
+ .option('id', {
102
+ describe: 'Override the auto-generated project id',
103
+ type: 'string',
104
+ })
105
+ .option('idempotency-key', {
106
+ type: 'string',
107
+ describe: 'Task #375 (HB#217) idempotency cache. Agent-scoped.',
108
+ })
109
+ .option('no-idempotency', {
110
+ type: 'boolean',
111
+ default: false,
112
+ describe: 'Bypass the idempotency cache.',
113
+ }),
114
+ handler: async (argv) => {
115
+ try {
116
+ // Resolve brief content.
117
+ let brief;
118
+ if (argv.briefFile) {
119
+ const p = (0, path_1.resolve)(argv.briefFile);
120
+ if (!(0, fs_1.existsSync)(p)) {
121
+ output.error(`--brief-file not found: ${p}`);
122
+ process.exitCode = 1;
123
+ return;
124
+ }
125
+ brief = (0, fs_1.readFileSync)(p, 'utf8').replace(/\s+$/, '');
126
+ }
127
+ else if (argv.brief !== undefined) {
128
+ brief = argv.brief.trim();
129
+ }
130
+ // Resolve proposedBy label up front — matches the append-lesson
131
+ // pattern. Default: lowercased wallet address from POP_PRIVATE_KEY.
132
+ let proposedBy;
133
+ if (argv.proposedBy) {
134
+ proposedBy = argv.proposedBy;
135
+ }
136
+ else {
137
+ const key = process.env.POP_PRIVATE_KEY;
138
+ if (!key) {
139
+ output.error('POP_PRIVATE_KEY not set — cannot derive default proposedBy. Pass --proposed-by explicitly.');
140
+ process.exitCode = 1;
141
+ return;
142
+ }
143
+ proposedBy = new ethers_1.ethers.Wallet(key).address.toLowerCase();
144
+ }
145
+ // Compute the id. Default is slugify(name); on collision with
146
+ // an existing project, append a unix-seconds suffix so a second
147
+ // caller with the same --name still succeeds.
148
+ const { doc: currentDoc } = await (0, brain_1.openBrainDoc)(argv.doc);
149
+ const existing = Array.isArray(currentDoc?.projects) ? currentDoc.projects : [];
150
+ const baseId = argv.id ?? (slugify(argv.name) || 'project');
151
+ let id = baseId;
152
+ if (existing.some(p => p?.id === id)) {
153
+ id = `${baseId}-${Math.floor(Date.now() / 1000)}`;
154
+ }
155
+ const now = Math.floor(Date.now() / 1000);
156
+ const stage = (argv.stage ?? 'propose');
157
+ // Task #375 (HB#217): idempotency check, agent-scoped
158
+ const idempKey = argv.idempotencyKey || (0, idempotency_1.argvToIdempotencyString)(argv);
159
+ if (!argv.noIdempotency) {
160
+ const cached = (0, idempotency_1.checkIdempotencyCache)(proposedBy, 'brain.newProject', idempKey);
161
+ if (cached) {
162
+ if (output.isJsonMode()) {
163
+ output.json({ status: 'ok', cached: true, ...cached });
164
+ }
165
+ else {
166
+ console.log('');
167
+ console.log(` Project already created (idempotency cache hit)`);
168
+ console.log(` id: ${cached.projectId}`);
169
+ console.log(` head: ${cached.headCid}`);
170
+ console.log('');
171
+ }
172
+ return;
173
+ }
174
+ }
175
+ // Route through the unified dispatcher (HB#324 ship-2).
176
+ const result = await (0, brain_ops_1.routedDispatch)({
177
+ type: 'newProject',
178
+ docId: argv.doc,
179
+ projectId: id,
180
+ name: argv.name,
181
+ brief,
182
+ stage,
183
+ proposedBy,
184
+ proposedAt: now,
185
+ });
186
+ if (!argv.noIdempotency) {
187
+ (0, idempotency_1.recordIdempotentResult)(proposedBy, 'brain.newProject', idempKey, {
188
+ docId: argv.doc,
189
+ projectId: id,
190
+ headCid: result.headCid,
191
+ });
192
+ }
193
+ if (output.isJsonMode()) {
194
+ output.json({
195
+ status: 'ok',
196
+ docId: argv.doc,
197
+ projectId: id,
198
+ name: argv.name,
199
+ stage,
200
+ proposedBy,
201
+ headCid: result.headCid,
202
+ envelopeAuthor: result.envelopeAuthor,
203
+ routedViaDaemon: result.routedViaDaemon,
204
+ });
205
+ }
206
+ else {
207
+ console.log('');
208
+ console.log(` Project created in ${argv.doc}`);
209
+ console.log(` id: ${id}`);
210
+ console.log(` name: ${argv.name}`);
211
+ console.log(` stage: ${stage}`);
212
+ console.log(` proposedBy: ${proposedBy}`);
213
+ console.log(` head: ${result.headCid}`);
214
+ console.log(` routed: ${result.routedViaDaemon ? 'via brain daemon' : 'in-process (no daemon)'}`);
215
+ console.log('');
216
+ }
217
+ }
218
+ catch (err) {
219
+ output.error(err.message);
220
+ process.exitCode = 1;
221
+ }
222
+ finally {
223
+ await (0, brain_1.stopBrainNode)();
224
+ }
225
+ },
226
+ };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * pop brain read — project a brain doc to JSON (or human-readable text).
3
+ *
4
+ * This is the read side of the brain CRDT substrate. Loads the Automerge
5
+ * doc identified by the current head CID in the manifest, verifies the
6
+ * signed envelope, and prints the current state. Useful for:
7
+ *
8
+ * - Verifying that a subscriber successfully synced a remote write
9
+ * (step 6 acceptance)
10
+ * - Inspecting the state of any local brain doc from the shell
11
+ * - Feeding the projection layer (step 7) when it replaces the
12
+ * hand-written shared.md / projects.md files
13
+ */
14
+ import type { Argv, ArgumentsCamelCase } from 'yargs';
15
+ interface ReadArgs {
16
+ doc: string;
17
+ }
18
+ export declare const readHandler: {
19
+ builder: (yargs: Argv) => Argv<{
20
+ doc: string;
21
+ }>;
22
+ handler: (argv: ArgumentsCamelCase<ReadArgs>) => Promise<void>;
23
+ };
24
+ export {};
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * pop brain read — project a brain doc to JSON (or human-readable text).
4
+ *
5
+ * This is the read side of the brain CRDT substrate. Loads the Automerge
6
+ * doc identified by the current head CID in the manifest, verifies the
7
+ * signed envelope, and prints the current state. Useful for:
8
+ *
9
+ * - Verifying that a subscriber successfully synced a remote write
10
+ * (step 6 acceptance)
11
+ * - Inspecting the state of any local brain doc from the shell
12
+ * - Feeding the projection layer (step 7) when it replaces the
13
+ * hand-written shared.md / projects.md files
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.readHandler = void 0;
50
+ const brain_1 = require("../../lib/brain");
51
+ const output = __importStar(require("@poa-box/cli/lib/output"));
52
+ exports.readHandler = {
53
+ builder: (yargs) => yargs.option('doc', {
54
+ describe: 'Brain document ID (e.g. pop.brain.shared)',
55
+ type: 'string',
56
+ demandOption: true,
57
+ }),
58
+ handler: async (argv) => {
59
+ try {
60
+ const { doc, headCid } = await (0, brain_1.readBrainDoc)(argv.doc);
61
+ if (output.isJsonMode()) {
62
+ output.json({ docId: argv.doc, headCid, doc });
63
+ }
64
+ else {
65
+ console.log('');
66
+ console.log(` Brain doc: ${argv.doc}`);
67
+ console.log(` Head CID: ${headCid ?? '(none — empty doc)'}`);
68
+ console.log(' ' + '─'.repeat(60));
69
+ console.log(JSON.stringify(doc, null, 2));
70
+ console.log('');
71
+ }
72
+ }
73
+ catch (err) {
74
+ output.error(err.message);
75
+ process.exitCode = 1;
76
+ }
77
+ finally {
78
+ await (0, brain_1.stopBrainNode)();
79
+ }
80
+ },
81
+ };