@jaybeeuu/agent-cortex 2.1.0 → 2.3.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 +411 -0
- package/README.md +33 -13
- package/agents/README.md +22 -10
- package/agents/plan/agent.md +2 -2
- package/agents/ralph/agent.md +126 -88
- package/agents/ralph/claude/frontmatter.json +3 -3
- package/agents/ralph/claude/wake.md +46 -0
- package/agents/ralph/pi/{polling.md → wake.md} +35 -20
- package/agents/ralph-plan/agent.md +23 -11
- package/bin/agent-cortex.mjs +66 -4
- package/bin/installers/claude.mjs +182 -68
- package/bin/installers/pi.mjs +200 -24
- package/extensions/agent-guardrails/README.md +1 -1
- package/extensions/agent-guardrails/guardrails.ts +1 -1
- package/extensions/agent-guardrails/index.ts +1 -1
- package/extensions/agent-modes/discover.test.ts +2 -2
- package/extensions/output-style/README.md +1 -1
- package/extensions/output-style/index.ts +2 -2
- package/extensions/output-style/output-style.ts +2 -2
- package/hooks/claude/scripts/notify.mjs +8 -1
- package/lib/cli.mjs +91 -2
- package/package.json +27 -3
- package/scripts/check-changeset.mjs +4 -0
- package/scripts/check-no-sync.mjs +5 -1
- package/scripts/lib/compose-agent.mjs +69 -9
- package/scripts/lib/fs.mjs +10 -2
- package/scripts/lib/migrate.mjs +736 -0
- package/scripts/lib/release-gates.mjs +21 -1
- package/scripts/lib/skill-frontmatter.mjs +23 -6
- package/scripts/lib/skill-links.mjs +52 -0
- package/scripts/lib/verify-worker-tree.mjs +25 -0
- package/scripts/resolve-publish-command.mjs +4 -0
- package/skills/engineering/design-an-interface/SKILL.md +3 -3
- package/skills/engineering/grill-with-docs/SKILL.md +3 -5
- package/skills/engineering/improve-codebase-architecture/SKILL.md +5 -4
- package/skills/engineering/tdd/SKILL.md +5 -7
- package/skills/engineering/technical-direction/SKILL.md +5 -5
- package/skills/planning/classify-bead/REFERENCE.md +3 -3
- package/skills/planning/classify-bead/SKILL.md +14 -9
- package/skills/planning/classify-bead/scripts/classify-bead.mjs +5 -4
- package/skills/planning/create-task/REFERENCE.md +52 -3
- package/skills/planning/create-task/SKILL.md +70 -26
- package/skills/planning/create-task/scripts/{create-chores.ts → create-chores.mjs} +48 -23
- package/skills/planning/flesh-out-idea/REFERENCE.md +116 -0
- package/skills/planning/flesh-out-idea/SKILL.md +146 -0
- package/skills/planning/prd-to-tasks/REFERENCE.md +54 -5
- package/skills/planning/prd-to-tasks/SKILL.md +22 -16
- package/skills/planning/record-idea/FORMAT.md +66 -17
- package/skills/planning/record-idea/SKILL.md +48 -24
- package/skills/planning/request-refactor-plan/SKILL.md +4 -4
- package/skills/planning/spec-to-tickets/SKILL.md +5 -5
- package/skills/planning/write-a-prd/SKILL.md +8 -9
- package/skills/planning/write-a-ticket/SKILL.md +4 -6
- package/skills/productivity/bd-tool/SKILL.md +15 -10
- package/skills/productivity/grill-me/SKILL.md +5 -0
- package/skills/productivity/init-beads/SKILL.md +3 -3
- package/skills/productivity/migration/REFERENCE.md +227 -0
- package/skills/productivity/migration/SKILL.md +188 -0
- package/skills/productivity/validate-agent-dir/scripts/{validate-agent-dir.ts → validate-agent-dir.mjs} +49 -28
- package/skills/productivity/write-a-skill/SKILL.md +5 -5
- package/skills/review/maintain-agent-docs/SKILL.md +5 -7
- package/skills/review/refactor-skill/SKILL.md +4 -3
- package/skills/review/review-security/SKILL.md +3 -5
- package/skills/style/style-code/SKILL.md +5 -10
- package/skills/style/style-comms/SKILL.md +5 -7
- package/skills/style/style-documentation/SKILL.md +5 -6
- package/skills/style/style-tests/SKILL.md +5 -7
- package/skills/workflow/git-workflow/SKILL.md +52 -26
- package/skills/workflow/hitl-collab/SKILL.md +4 -6
- package/skills/workflow/plan/SKILL.md +41 -30
- package/skills/workflow/ralph/REFERENCE.md +182 -51
- package/skills/workflow/ralph/SKILL.md +89 -50
- package/skills/workflow/run-pipeline-stage/SKILL.md +72 -71
- package/skills/workflow/run-pipeline-stage/playbooks/code.md +11 -0
- package/skills/workflow/run-pipeline-stage/playbooks/document.md +12 -0
- package/skills/workflow/run-pipeline-stage/playbooks/fix.md +12 -0
- package/skills/workflow/run-pipeline-stage/playbooks/review.md +11 -0
- package/skills/workflow/run-pipeline-stage/playbooks/verify.md +13 -0
- package/skills/workflow/run-pipeline-stage/prompts/stage-runner.md +73 -1
- package/token-map.README.md +9 -4
- package/token-map.json +13 -6
- package/.agents/skills/style-versioning/SKILL.md +0 -172
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.github/workflows/ci.yml +0 -302
- package/.junie/guidelines.md +0 -87
- package/.junie/mcp/mcp.json +0 -10
- package/AGENTS.md +0 -153
- package/agents/ralph/claude/polling.md +0 -31
- package/agents-native/ralph.md +0 -203
- package/docs/claude-hooks.md +0 -144
- package/docs/commit-signing.md +0 -231
- package/docs/ideas/auto-model-selection.md +0 -75
- package/docs/ideas/ban-content-assertion-tests.md +0 -75
- package/docs/ideas/bead-progress-notes-for-session-resumption.md +0 -75
- package/docs/ideas/classify-beads-via-deterministic-script-with-agent-fallback.md +0 -70
- package/docs/ideas/ctx-alias-version-and-update-commands.md +0 -65
- package/docs/ideas/ctx-feedback-command-for-capturing-ideas-from-any-machine.md +0 -67
- package/docs/ideas/dedicated-review-tests-skill.md +0 -71
- package/docs/ideas/deployment-beads-for-post-merge-pipelines.md +0 -36
- package/docs/ideas/design-and-delivery-as-separate-bead-trees/design-and-delivery-as-separate-bead-trees.md +0 -99
- package/docs/ideas/enable-signed-commit-enforcement-changesets-bot-commits-must-be-signed.md +0 -42
- package/docs/ideas/extension-manifests.md +0 -121
- package/docs/ideas/improve-pi-system-prompt.md +0 -43
- package/docs/ideas/map-every-cortex-tool-to-a-real-pi-equivalent.md +0 -95
- package/docs/ideas/model-selector.md +0 -40
- package/docs/ideas/pi-task-memory.md +0 -34
- package/docs/ideas/qa-extension.md +0 -43
- package/docs/ideas/record-idea-implementation-questions.md +0 -68
- package/docs/ideas/record-ideas-as-beads.md +0 -115
- package/docs/ideas/self-flushing-session.md +0 -72
- package/docs/ideas/state-in-beads-not-md.md +0 -69
- package/docs/ideas/subagent-visibility.md +0 -72
- package/docs/ideas/task-session-context.local.md +0 -95
- package/docs/ideas/task-session-context.md +0 -89
- package/docs/ideas/test-data-creation-helpers.md +0 -92
- package/docs/ideas/test-planning-stage-bead-in-feature-planning.md +0 -68
- package/docs/ideas/unified-ci-pipeline.md +0 -28
- package/docs/ideas/unify-the-ticket-and-task-decomposition-workflows.md +0 -71
- package/docs/ideas/worktree-housekeeping-for-closed-beads.md +0 -39
- package/docs/inspirations.md +0 -33
- package/docs/research/disable-model-invocation.md +0 -82
- package/docs/research/gas-city-research.md +0 -256
- package/docs/research/skill-design-research.md +0 -286
- package/docs/research/skill-improvements-analysis.md +0 -117
- package/docs/research/skill-patterns-research.md +0 -24
- package/docs/skills/skill-anatomy.md +0 -200
- package/docs/technical-direction/harness-config.md +0 -177
- package/docs/technical-direction/migration-execution-labels.md +0 -177
- package/docs/user-preferences.md +0 -17
- package/extensions/agent-guardrails/tsconfig.json +0 -12
- package/extensions/agent-modes/tsconfig.json +0 -12
- package/extensions/output-style/tsconfig.json +0 -12
- package/extensions/session-start/tsconfig.json +0 -12
- package/extensions/skill-stats/pnpm-lock.yaml +0 -329
- package/extensions/skill-stats/tsconfig.json +0 -12
- package/extensions/subagent/agents/planner.md +0 -37
- package/extensions/subagent/agents/reviewer.md +0 -35
- package/extensions/subagent/agents/scout.md +0 -50
- package/extensions/subagent/agents/worker.md +0 -24
- package/extensions/subagent/prompts/implement-and-review.md +0 -10
- package/extensions/subagent/prompts/implement.md +0 -10
- package/extensions/subagent/prompts/scout-and-plan.md +0 -9
- package/extensions/subagent/tsconfig.json +0 -12
- package/pi/keybindings.json +0 -3
- package/pi/settings.json +0 -34
- package/pnpm-workspace.yaml +0 -14
- package/skills/planning/create-task/scripts/package.json +0 -14
- package/skills/planning/create-task/scripts/pnpm-lock.yaml +0 -342
- package/skills/planning/create-task/scripts/tsconfig.json +0 -12
- package/skills/planning/record-idea/scripts/new-idea.sh +0 -89
- package/skills/productivity/validate-agent-dir/scripts/package.json +0 -15
- package/skills/productivity/validate-agent-dir/scripts/pnpm-lock.yaml +0 -329
- package/skills/productivity/validate-agent-dir/scripts/pnpm-workspace.yaml +0 -2
- package/skills/productivity/validate-agent-dir/scripts/tsconfig.json +0 -19
- package/skills/productivity/validate-agent-dir/scripts/validate-agent-dir.test.ts +0 -317
- package/skills/workflow/run-pipeline-stage/REFERENCE.md +0 -22
- package/skills/workflow/run-pipeline-stage/scripts/generate-progress.test.ts +0 -682
- package/skills/workflow/run-pipeline-stage/scripts/generate-progress.ts +0 -588
- package/skills/workflow/run-pipeline-stage/scripts/package.json +0 -15
- package/skills/workflow/run-pipeline-stage/scripts/pnpm-lock.yaml +0 -342
- package/skills/workflow/run-pipeline-stage/scripts/pnpm-workspace.yaml +0 -2
- package/skills/workflow/run-pipeline-stage/scripts/tsconfig.json +0 -12
- package/test/classify-bead.test.mjs +0 -277
- package/test/claude-installer.test.mjs +0 -308
- package/test/cli.test.mjs +0 -314
- package/test/create-chores.test.mjs +0 -85
- package/test/fake-bd.test.mjs +0 -72
- package/test/fs.test.mjs +0 -76
- package/test/helpers/fake-bd.mjs +0 -87
- package/test/helpers/fake-claude.mjs +0 -152
- package/test/helpers/fake-pi.mjs +0 -132
- package/test/installer.test.mjs +0 -806
- package/test/notify-hook.test.mjs +0 -63
- package/test/pending-gates.test.mjs +0 -147
- package/test/pi-installer.test.mjs +0 -785
- package/test/pi-register.test.mjs +0 -342
- package/test/pi-sweep.test.mjs +0 -619
- package/test/release-gates.test.mjs +0 -190
- package/test/skill-frontmatter.test.mjs +0 -137
- package/test/verify-worker-tree.test.mjs +0 -120
package/bin/installers/pi.mjs
CHANGED
|
@@ -115,6 +115,48 @@ import { isDirectory, isFile } from "../../scripts/lib/fs.mjs";
|
|
|
115
115
|
|
|
116
116
|
const PI = "pi";
|
|
117
117
|
|
|
118
|
+
/** @typedef {import("../../scripts/lib/compose-agent.mjs").TokenMap} TokenMap */
|
|
119
|
+
/** @typedef {import("../../scripts/lib/compose-agent.mjs").ComposedAgent} ComposedAgent */
|
|
120
|
+
/** @typedef {(msg: string) => void} Warn */
|
|
121
|
+
/** @typedef {(name: string, fromDir?: string) => string | Promise<string>} ResolvePackage */
|
|
122
|
+
/** @typedef {{ name: string, dir: string }} ResolvedPackage */
|
|
123
|
+
/** @typedef {{ extensions: string[], skills: string[] }} PackageResources */
|
|
124
|
+
/** @typedef {{ packages: ResolvedPackage[], resources: PackageResources }} PiDependencies */
|
|
125
|
+
/** @typedef {(content: string) => string} ContentTransform */
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The generated package's pi manifest (`//` carries the generated-file marker).
|
|
129
|
+
* @typedef {{ "//": string, name: string, version: string, private: boolean,
|
|
130
|
+
* description: string, type: string, keywords: string[],
|
|
131
|
+
* pi: { extensions: string[], skills: string[] } }} PiManifest
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Staged-package summary returned by installPi.
|
|
136
|
+
* @typedef {object} InstallPiResult
|
|
137
|
+
* @property {string} output
|
|
138
|
+
* @property {{ name: string, filePath: string }[]} agents
|
|
139
|
+
* @property {{ skills: number, md: number, files: number, dir: string }} skills
|
|
140
|
+
* @property {{ files: number, dir: string }} extensions
|
|
141
|
+
* @property {{ dir: string, bundled: string[], staged: string[], resources: PackageResources }} packages
|
|
142
|
+
* @property {{ path: string, manifest: PiManifest }} manifest
|
|
143
|
+
* @property {string} tokenMap
|
|
144
|
+
* @property {string[]} warnings
|
|
145
|
+
* @property {boolean} dryRun
|
|
146
|
+
*/
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Registration plan/outcome returned by registerPi.
|
|
150
|
+
* @typedef {object} RegisterPiResult
|
|
151
|
+
* @property {string} output
|
|
152
|
+
* @property {string} piBin
|
|
153
|
+
* @property {string} settingsPath
|
|
154
|
+
* @property {boolean} registered
|
|
155
|
+
* @property {string[]} removed
|
|
156
|
+
* @property {string[][]} commands
|
|
157
|
+
* @property {boolean} dryRun
|
|
158
|
+
*/
|
|
159
|
+
|
|
118
160
|
// The token-map contract version this installer implements (token-map.json
|
|
119
161
|
// "version" field). A higher map version is rejected: the contract must be
|
|
120
162
|
// extended before this installer can trust it.
|
|
@@ -152,7 +194,7 @@ const DEFAULT_SETTINGS_PATH = join(homedir(), ".pi", "agent", "settings.json");
|
|
|
152
194
|
// npm:, git:, https/github URLs, and built-in extension names).
|
|
153
195
|
const NON_LOCAL_PREFIXES = ["npm:", "git:", "github:", "http:", "https:", "ssh:", "builtin:"];
|
|
154
196
|
|
|
155
|
-
const DEFAULT_WARN = (msg) => console.warn(`[pi-installer] ${msg}`);
|
|
197
|
+
const DEFAULT_WARN = (/** @type {string} */ msg) => console.warn(`[pi-installer] ${msg}`);
|
|
156
198
|
|
|
157
199
|
// Artifacts the pre-staged-model installer wrote into pi's own user directory
|
|
158
200
|
// that the package root can no longer name on its own: the orphaned
|
|
@@ -197,7 +239,7 @@ const DEFAULT_LEGACY_SWEEP_RECORD = join(homedir(), ".agent-cortex", "legacy-swe
|
|
|
197
239
|
* skills: {skills: number, md: number, files: number, dir: string},
|
|
198
240
|
* extensions: {files: number, dir: string},
|
|
199
241
|
* packages: {dir: string, bundled: string[], staged: string[], resources: {extensions: string[], skills: string[]}},
|
|
200
|
-
* manifest: {path: string, manifest:
|
|
242
|
+
* manifest: {path: string, manifest: PiManifest},
|
|
201
243
|
* tokenMap: string, warnings: string[], dryRun: boolean }>}
|
|
202
244
|
*/
|
|
203
245
|
export async function installPi(options = {}) {
|
|
@@ -205,8 +247,9 @@ export async function installPi(options = {}) {
|
|
|
205
247
|
const output = options.output ?? DEFAULT_OUTPUT;
|
|
206
248
|
const dryRun = options.dryRun ?? false;
|
|
207
249
|
const resolvePackage = options.resolvePackage ?? resolveFromNode;
|
|
250
|
+
/** @type {string[]} */
|
|
208
251
|
const warnings = [];
|
|
209
|
-
const warn = (msg) => {
|
|
252
|
+
const warn = (/** @type {string} */ msg) => {
|
|
210
253
|
warnings.push(msg);
|
|
211
254
|
(options.warn ?? DEFAULT_WARN)(msg);
|
|
212
255
|
};
|
|
@@ -222,7 +265,7 @@ export async function installPi(options = {}) {
|
|
|
222
265
|
);
|
|
223
266
|
}
|
|
224
267
|
|
|
225
|
-
const pluginRoot = options.pluginRoot ?? tokenMap.paths.plugin_root?.[PI];
|
|
268
|
+
const pluginRoot = options.pluginRoot ?? /** @type {string | undefined} */ (tokenMap.paths.plugin_root?.[PI]);
|
|
226
269
|
|
|
227
270
|
// Resolve the bundled packages before clearing the output: an unresolvable
|
|
228
271
|
// dependency must fail the install without destroying the previous staging.
|
|
@@ -264,6 +307,11 @@ export async function installPi(options = {}) {
|
|
|
264
307
|
|
|
265
308
|
// ─── Agents ──────────────────────────────────────────────────────────────────
|
|
266
309
|
|
|
310
|
+
/**
|
|
311
|
+
* @param {{ root: string, agentsDir: string, dryRun: boolean, pluginRoot: string | undefined,
|
|
312
|
+
* tokenMap: TokenMap, warn: Warn }} options
|
|
313
|
+
* @returns {Promise<{ name: string, filePath: string }[]>}
|
|
314
|
+
*/
|
|
267
315
|
async function installAgents({ root, agentsDir, dryRun, pluginRoot, tokenMap, warn }) {
|
|
268
316
|
const written = [];
|
|
269
317
|
const agentsSrc = join(root, "agents");
|
|
@@ -295,6 +343,12 @@ async function installAgents({ root, agentsDir, dryRun, pluginRoot, tokenMap, wa
|
|
|
295
343
|
return written;
|
|
296
344
|
}
|
|
297
345
|
|
|
346
|
+
/**
|
|
347
|
+
* @param {string} name
|
|
348
|
+
* @param {ComposedAgent} fm
|
|
349
|
+
* @param {string[]} tools
|
|
350
|
+
* @returns {string}
|
|
351
|
+
*/
|
|
298
352
|
function renderPiAgent(name, fm, tools) {
|
|
299
353
|
const lines = [
|
|
300
354
|
"---",
|
|
@@ -316,10 +370,16 @@ function renderPiAgent(name, fm, tools) {
|
|
|
316
370
|
|
|
317
371
|
// ─── Skills ──────────────────────────────────────────────────────────────────
|
|
318
372
|
|
|
373
|
+
/**
|
|
374
|
+
* @param {{ root: string, skillsDir: string, dryRun: boolean, pluginRoot: string | undefined,
|
|
375
|
+
* tokenMap: TokenMap, warn: Warn }} options
|
|
376
|
+
* @returns {Promise<{ skills: number, md: number, files: number, dir: string }>}
|
|
377
|
+
*/
|
|
319
378
|
async function installSkills({ root, skillsDir, dryRun, pluginRoot, tokenMap, warn }) {
|
|
320
379
|
const skillsSrc = join(root, "skills");
|
|
321
380
|
if (!(await isDirectory(skillsSrc))) return { skills: 0, md: 0, files: 0, dir: skillsDir };
|
|
322
381
|
|
|
382
|
+
/** @param {string} content @returns {string} */
|
|
323
383
|
const transform = (content) =>
|
|
324
384
|
substituteTokens(content, PI, tokenMap, { dropNullTools: true, pluginRoot, resolveRelativePaths: true, warn });
|
|
325
385
|
|
|
@@ -349,6 +409,8 @@ async function installSkills({ root, skillsDir, dryRun, pluginRoot, tokenMap, wa
|
|
|
349
409
|
* dependency tree is needed. `node_modules` (pnpm workspace links) and symlinks
|
|
350
410
|
* are never copied: the staged package owns its dependencies and must contain
|
|
351
411
|
* real files only.
|
|
412
|
+
* @param {{ root: string, output: string, dryRun: boolean }} options
|
|
413
|
+
* @returns {Promise<{ files: number, dir: string }>}
|
|
352
414
|
*/
|
|
353
415
|
async function installExtensions({ root, output, dryRun }) {
|
|
354
416
|
const src = join(root, "extensions");
|
|
@@ -363,6 +425,8 @@ async function installExtensions({ root, output, dryRun }) {
|
|
|
363
425
|
/**
|
|
364
426
|
* Ship token-map.json alongside agents/ — agent-modes loads it from the package
|
|
365
427
|
* root, so the staged extensions keep the same source of truth as the checkout.
|
|
428
|
+
* @param {{ output: string, tokenMap: TokenMap, dryRun: boolean }} options
|
|
429
|
+
* @returns {Promise<string>}
|
|
366
430
|
*/
|
|
367
431
|
async function installTokenMap({ output, tokenMap, dryRun }) {
|
|
368
432
|
const path = join(output, "token-map.json");
|
|
@@ -381,10 +445,13 @@ async function installTokenMap({ output, tokenMap, dryRun }) {
|
|
|
381
445
|
* generated manifest loads them. Ordinary dependencies are left to the usual
|
|
382
446
|
* module resolution.
|
|
383
447
|
*
|
|
448
|
+
* @param {{ root: string, resolvePackage: ResolvePackage }} options
|
|
384
449
|
* @returns {Promise<{packages: {name: string, dir: string}[], resources: {extensions: string[], skills: string[]}}>}
|
|
385
450
|
*/
|
|
386
451
|
async function resolvePiPackages({ root, resolvePackage }) {
|
|
452
|
+
/** @type {ResolvedPackage[]} */
|
|
387
453
|
const packages = [];
|
|
454
|
+
/** @type {PackageResources} */
|
|
388
455
|
const resources = { extensions: [], skills: [] };
|
|
389
456
|
|
|
390
457
|
for (const name of await readDirectDependencies(root)) {
|
|
@@ -393,7 +460,7 @@ async function resolvePiPackages({ root, resolvePackage }) {
|
|
|
393
460
|
if (manifest === null || manifest.pi === undefined) continue;
|
|
394
461
|
|
|
395
462
|
packages.push({ name, dir });
|
|
396
|
-
for (const type of ["extensions", "skills"]) {
|
|
463
|
+
for (const type of /** @type {("extensions" | "skills")[]} */ (["extensions", "skills"])) {
|
|
397
464
|
for (const entry of asStringList(manifest.pi[type])) {
|
|
398
465
|
resources[type].push(`node_modules/${name}/${stripRelative(entry)}`);
|
|
399
466
|
}
|
|
@@ -410,13 +477,17 @@ async function resolvePiPackages({ root, resolvePackage }) {
|
|
|
410
477
|
* source layout is irrelevant. Copies are real files (never symlinks) and stay
|
|
411
478
|
* flat, matching what pi loads from a local package; a package that resolves to
|
|
412
479
|
* two different versions keeps the first and warns.
|
|
480
|
+
* @param {{ dependencies: PiDependencies, output: string, resolvePackage: ResolvePackage,
|
|
481
|
+
* dryRun: boolean, warn: Warn }} options
|
|
482
|
+
* @returns {Promise<{ dir: string, bundled: string[], staged: string[], resources: PackageResources }>}
|
|
413
483
|
*/
|
|
414
484
|
async function stagePiPackages({ dependencies, output, resolvePackage, dryRun, warn }) {
|
|
485
|
+
/** @type {Map<string, string>} */
|
|
415
486
|
const staged = new Map();
|
|
416
487
|
const pending = dependencies.packages.map(({ name, dir }) => ({ name, dir }));
|
|
417
488
|
|
|
418
489
|
while (pending.length > 0) {
|
|
419
|
-
const { name, dir } = pending.shift();
|
|
490
|
+
const { name, dir } = /** @type {ResolvedPackage} */ (pending.shift());
|
|
420
491
|
const seen = staged.get(name);
|
|
421
492
|
if (seen !== undefined) {
|
|
422
493
|
if (seen !== dir) warn(`pi package "${name}" resolves to two directories (${seen}, ${dir}) — keeping the first`);
|
|
@@ -445,9 +516,13 @@ async function stagePiPackages({ dependencies, output, resolvePackage, dryRun, w
|
|
|
445
516
|
* construction. A package's `package.json` is not always exported, so the
|
|
446
517
|
* package directory is found by resolving an entry point and walking up to the
|
|
447
518
|
* enclosing manifest that names it.
|
|
519
|
+
* @param {string} name
|
|
520
|
+
* @param {string} [fromDir]
|
|
521
|
+
* @returns {Promise<string>}
|
|
448
522
|
*/
|
|
449
523
|
async function resolveFromNode(name, fromDir = PACKAGE_ROOT) {
|
|
450
524
|
const req = createRequire(join(fromDir, "package.json"));
|
|
525
|
+
/** @type {string | null} */
|
|
451
526
|
let entry = null;
|
|
452
527
|
for (const specifier of [name, `${name}/package.json`]) {
|
|
453
528
|
try {
|
|
@@ -471,11 +546,19 @@ async function resolveFromNode(name, fromDir = PACKAGE_ROOT) {
|
|
|
471
546
|
}
|
|
472
547
|
}
|
|
473
548
|
|
|
549
|
+
/**
|
|
550
|
+
* @param {string} root
|
|
551
|
+
* @returns {Promise<string[]>}
|
|
552
|
+
*/
|
|
474
553
|
async function readDirectDependencies(root) {
|
|
475
554
|
const manifest = await readJsonObject(join(root, "package.json"));
|
|
476
555
|
return manifest === null ? [] : Object.keys(manifest.dependencies ?? {});
|
|
477
556
|
}
|
|
478
557
|
|
|
558
|
+
/**
|
|
559
|
+
* @param {string} dir
|
|
560
|
+
* @returns {Promise<string[]>}
|
|
561
|
+
*/
|
|
479
562
|
async function readDependencyNames(dir) {
|
|
480
563
|
const manifest = await readJsonObject(join(dir, "package.json"));
|
|
481
564
|
return manifest === null ? [] : Object.keys(manifest.dependencies ?? {});
|
|
@@ -515,8 +598,7 @@ async function readDependencyNames(dir) {
|
|
|
515
598
|
* @param {boolean} [options.dryRun] Plan and report without spawning anything
|
|
516
599
|
* @param {boolean} [options.requireRegister] Throw when registration cannot run
|
|
517
600
|
* @param {(msg: string) => void} [options.warn] Warning sink (default: console.warn)
|
|
518
|
-
* @returns {Promise<
|
|
519
|
-
* removed: string[], commands: string[][], dryRun: boolean }>}
|
|
601
|
+
* @returns {Promise<RegisterPiResult>}
|
|
520
602
|
*/
|
|
521
603
|
export async function registerPi({
|
|
522
604
|
root = PACKAGE_ROOT,
|
|
@@ -536,7 +618,7 @@ export async function registerPi({
|
|
|
536
618
|
// runs it: drop the superseded sources, then register the staged package.
|
|
537
619
|
const manualCommands = [...stale.map(({ path }) => `${piBin} remove ${path}`), `${piBin} install ${stagedPath}`];
|
|
538
620
|
|
|
539
|
-
const summary = { output: stagedPath, piBin, settingsPath, registered: false, removed: [], commands: [], dryRun };
|
|
621
|
+
const summary = /** @type {RegisterPiResult} */ ({ output: stagedPath, piBin, settingsPath, registered: false, removed: [], commands: [], dryRun });
|
|
540
622
|
for (const { path } of stale) summary.commands.push(["remove", path]);
|
|
541
623
|
if (!registered) summary.commands.push(["install", stagedPath]);
|
|
542
624
|
|
|
@@ -583,7 +665,7 @@ export async function registerPi({
|
|
|
583
665
|
// checkout and the staged package at once, and every re-run would repeat
|
|
584
666
|
// the same non-converging cycle. Fail instead of reporting success.
|
|
585
667
|
throw new Error(
|
|
586
|
-
`${err.message} — the superseded source is still configured: ${path}. ` +
|
|
668
|
+
`${/** @type {Error} */ (err).message} — the superseded source is still configured: ${path}. ` +
|
|
587
669
|
`Remove it with "${piBin} remove ${path}" and re-run`,
|
|
588
670
|
);
|
|
589
671
|
}
|
|
@@ -612,6 +694,8 @@ export async function registerPi({
|
|
|
612
694
|
/**
|
|
613
695
|
* Local package sources configured in pi's settings file, in order. pi stores
|
|
614
696
|
* each entry as a source string or a `{ source, … }` filter object.
|
|
697
|
+
* @param {string} settingsPath
|
|
698
|
+
* @returns {Promise<string[]>}
|
|
615
699
|
*/
|
|
616
700
|
async function readConfiguredPackages(settingsPath) {
|
|
617
701
|
const settings = await readJsonObject(settingsPath);
|
|
@@ -620,11 +704,19 @@ async function readConfiguredPackages(settingsPath) {
|
|
|
620
704
|
return packages.map(packageSource).filter((source) => source !== undefined);
|
|
621
705
|
}
|
|
622
706
|
|
|
707
|
+
/**
|
|
708
|
+
* @param {any} entry
|
|
709
|
+
* @returns {string | undefined}
|
|
710
|
+
*/
|
|
623
711
|
function packageSource(entry) {
|
|
624
712
|
if (typeof entry === "string") return entry;
|
|
625
713
|
return typeof entry?.source === "string" ? entry.source : undefined;
|
|
626
714
|
}
|
|
627
715
|
|
|
716
|
+
/**
|
|
717
|
+
* @param {string} source
|
|
718
|
+
* @returns {boolean}
|
|
719
|
+
*/
|
|
628
720
|
function isLocalSource(source) {
|
|
629
721
|
return !NON_LOCAL_PREFIXES.some((prefix) => source.trim().startsWith(prefix));
|
|
630
722
|
}
|
|
@@ -632,6 +724,9 @@ function isLocalSource(source) {
|
|
|
632
724
|
/**
|
|
633
725
|
* Resolve a configured source the way pi does: relative to the directory
|
|
634
726
|
* holding the settings file, with `~` expanded.
|
|
727
|
+
* @param {string} source
|
|
728
|
+
* @param {string} settingsDir
|
|
729
|
+
* @returns {string}
|
|
635
730
|
*/
|
|
636
731
|
function resolveSourcePath(source, settingsDir) {
|
|
637
732
|
const trimmed = source.trim();
|
|
@@ -645,9 +740,12 @@ function resolveSourcePath(source, settingsDir) {
|
|
|
645
740
|
* whose directory is gone is left alone: a missing path cannot be told apart
|
|
646
741
|
* from a temporarily absent one, and removing it could drop a package the user
|
|
647
742
|
* still wants.
|
|
743
|
+
* @param {{ sources: string[], settingsPath: string, stagedPath: string, rawPackageName: string | undefined }} options
|
|
744
|
+
* @returns {Promise<{ registered: boolean, stale: { path: string }[] }>}
|
|
648
745
|
*/
|
|
649
746
|
async function classifySources({ sources, settingsPath, stagedPath, rawPackageName }) {
|
|
650
747
|
const settingsDir = dirname(settingsPath);
|
|
748
|
+
/** @type {{ path: string }[]} */
|
|
651
749
|
const stale = [];
|
|
652
750
|
let registered = false;
|
|
653
751
|
for (const source of sources) {
|
|
@@ -666,6 +764,8 @@ async function classifySources({ sources, settingsPath, stagedPath, rawPackageNa
|
|
|
666
764
|
/**
|
|
667
765
|
* Probe whether the `pi` CLI can be run at all, with output suppressed.
|
|
668
766
|
* Resolves `{ ok: true }` or `{ ok: false, detail }` naming the failure.
|
|
767
|
+
* @param {string} piBin
|
|
768
|
+
* @returns {Promise<{ ok: true } | { ok: false, detail: string }>}
|
|
669
769
|
*/
|
|
670
770
|
function probePi(piBin) {
|
|
671
771
|
return new Promise((resolve) => {
|
|
@@ -683,7 +783,12 @@ function probePi(piBin) {
|
|
|
683
783
|
});
|
|
684
784
|
}
|
|
685
785
|
|
|
686
|
-
/**
|
|
786
|
+
/**
|
|
787
|
+
* Run one pi package command, inheriting its output; rejects on failure.
|
|
788
|
+
* @param {string} piBin
|
|
789
|
+
* @param {string[]} args
|
|
790
|
+
* @returns {Promise<void>}
|
|
791
|
+
*/
|
|
687
792
|
function runPiCommand(piBin, args) {
|
|
688
793
|
const label = `${piBin} ${args.join(" ")}`;
|
|
689
794
|
return new Promise((resolve, reject) => {
|
|
@@ -747,15 +852,23 @@ export async function sweepLegacyPiUserDirs({
|
|
|
747
852
|
recordPath = DEFAULT_LEGACY_SWEEP_RECORD,
|
|
748
853
|
dryRun = false,
|
|
749
854
|
warn = DEFAULT_WARN,
|
|
750
|
-
}
|
|
855
|
+
}) {
|
|
856
|
+
/** @type {string[]} */
|
|
751
857
|
const removed = [];
|
|
858
|
+
/** @type {string[]} */
|
|
752
859
|
const kept = [];
|
|
860
|
+
/** @type {string[]} */
|
|
753
861
|
const failed = [];
|
|
754
862
|
|
|
755
863
|
if (!(await isDirectory(piUserDir))) return { removed, kept };
|
|
756
864
|
if (await hasMigrated({ recordPath, piUserDir, warn })) return { removed, kept };
|
|
757
865
|
|
|
758
866
|
// Commands that record what happened and either delete the path or keep it.
|
|
867
|
+
/**
|
|
868
|
+
* @param {string} path
|
|
869
|
+
* @param {boolean} isLegacy
|
|
870
|
+
* @returns {Promise<void>}
|
|
871
|
+
*/
|
|
759
872
|
const sweep = async (path, isLegacy) => {
|
|
760
873
|
if (!isLegacy) {
|
|
761
874
|
kept.push(path);
|
|
@@ -769,7 +882,7 @@ export async function sweepLegacyPiUserDirs({
|
|
|
769
882
|
await rm(path, { recursive: true, force: true });
|
|
770
883
|
removed.push(path);
|
|
771
884
|
} catch (err) {
|
|
772
|
-
warn(`could not remove the legacy artifact ${path}: ${err.message}`);
|
|
885
|
+
warn(`could not remove the legacy artifact ${path}: ${/** @type {Error} */ (err).message}`);
|
|
773
886
|
kept.push(path);
|
|
774
887
|
failed.push(path);
|
|
775
888
|
}
|
|
@@ -782,6 +895,7 @@ export async function sweepLegacyPiUserDirs({
|
|
|
782
895
|
// attribute what it cannot see, so it warns and counts the directory as a
|
|
783
896
|
// failure — which is what keeps the migration unrecorded, so the next install
|
|
784
897
|
// retries once the permissions allow it.
|
|
898
|
+
/** @param {string} dir */
|
|
785
899
|
const unreadable = (dir) => {
|
|
786
900
|
warn(
|
|
787
901
|
`could not list ${dir} — the legacy sweep cannot tell its contents from the user's, ` +
|
|
@@ -792,6 +906,10 @@ export async function sweepLegacyPiUserDirs({
|
|
|
792
906
|
|
|
793
907
|
// Entries of a sweep site: none for a directory that is absent, and a recorded
|
|
794
908
|
// failure for one that exists but cannot be read.
|
|
909
|
+
/**
|
|
910
|
+
* @param {string} dir
|
|
911
|
+
* @returns {Promise<string[]>}
|
|
912
|
+
*/
|
|
795
913
|
const list = async (dir) => {
|
|
796
914
|
const entries = await listDir(dir);
|
|
797
915
|
if (entries === null) unreadable(dir);
|
|
@@ -861,14 +979,16 @@ export async function sweepLegacyPiUserDirs({
|
|
|
861
979
|
* A record that exists but cannot be read is treated as "already migrated":
|
|
862
980
|
* the state is ambiguous and a deletion is not recoverable, so the sweep never
|
|
863
981
|
* runs on an unreadable record.
|
|
982
|
+
* @param {{ recordPath: string, piUserDir: string, warn: Warn }} options
|
|
983
|
+
* @returns {Promise<boolean>}
|
|
864
984
|
*/
|
|
865
985
|
async function hasMigrated({ recordPath, piUserDir, warn }) {
|
|
866
986
|
let raw;
|
|
867
987
|
try {
|
|
868
988
|
raw = await readFile(recordPath, "utf-8");
|
|
869
989
|
} catch (err) {
|
|
870
|
-
if (err.code === "ENOENT") return false;
|
|
871
|
-
warn(`could not read the legacy sweep record ${recordPath}: ${err.message} — the migration will not run again`);
|
|
990
|
+
if (/** @type {NodeJS.ErrnoException} */ (err).code === "ENOENT") return false;
|
|
991
|
+
warn(`could not read the legacy sweep record ${recordPath}: ${/** @type {Error} */ (err).message} — the migration will not run again`);
|
|
872
992
|
return true;
|
|
873
993
|
}
|
|
874
994
|
const entries = parseRecordEntries(raw);
|
|
@@ -884,6 +1004,8 @@ async function hasMigrated({ recordPath, piUserDir, warn }) {
|
|
|
884
1004
|
* — another pi config may have migrated already and must not be re-run. A write
|
|
885
1005
|
* failure warns instead of throwing: registration already succeeded, so an
|
|
886
1006
|
* unwritable record is not a reason to fail the install.
|
|
1007
|
+
* @param {{ recordPath: string, piUserDir: string, warn: Warn }} options
|
|
1008
|
+
* @returns {Promise<void>}
|
|
887
1009
|
*/
|
|
888
1010
|
async function recordMigration({ recordPath, piUserDir, warn }) {
|
|
889
1011
|
const entries = await readRecordEntries(recordPath);
|
|
@@ -893,12 +1015,16 @@ async function recordMigration({ recordPath, piUserDir, warn }) {
|
|
|
893
1015
|
await writeFile(recordPath, `${JSON.stringify(entries, null, 2)}\n`);
|
|
894
1016
|
} catch (err) {
|
|
895
1017
|
warn(
|
|
896
|
-
`could not record the legacy sweep at ${recordPath}: ${err.message} — a later install may run the migration again`,
|
|
1018
|
+
`could not record the legacy sweep at ${recordPath}: ${/** @type {Error} */ (err).message} — a later install may run the migration again`,
|
|
897
1019
|
);
|
|
898
1020
|
}
|
|
899
1021
|
}
|
|
900
1022
|
|
|
901
|
-
/**
|
|
1023
|
+
/**
|
|
1024
|
+
* Record entries keyed by pi user directory; `{}` when it is absent or unusable.
|
|
1025
|
+
* @param {string} recordPath
|
|
1026
|
+
* @returns {Promise<Record<string, any>>}
|
|
1027
|
+
*/
|
|
902
1028
|
async function readRecordEntries(recordPath) {
|
|
903
1029
|
try {
|
|
904
1030
|
return parseRecordEntries(await readFile(recordPath, "utf-8")) ?? {};
|
|
@@ -907,7 +1033,11 @@ async function readRecordEntries(recordPath) {
|
|
|
907
1033
|
}
|
|
908
1034
|
}
|
|
909
1035
|
|
|
910
|
-
/**
|
|
1036
|
+
/**
|
|
1037
|
+
* The record's `{ "<piUserDir>": entry }` object, or null when the text is not one.
|
|
1038
|
+
* @param {string} raw
|
|
1039
|
+
* @returns {Record<string, any> | null}
|
|
1040
|
+
*/
|
|
911
1041
|
function parseRecordEntries(raw) {
|
|
912
1042
|
let parsed;
|
|
913
1043
|
try {
|
|
@@ -918,7 +1048,11 @@ function parseRecordEntries(raw) {
|
|
|
918
1048
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : null;
|
|
919
1049
|
}
|
|
920
1050
|
|
|
921
|
-
/**
|
|
1051
|
+
/**
|
|
1052
|
+
* Whether `path` is a directory without following a symlink to one.
|
|
1053
|
+
* @param {string} path
|
|
1054
|
+
* @returns {Promise<boolean>}
|
|
1055
|
+
*/
|
|
922
1056
|
async function isRealDirectory(path) {
|
|
923
1057
|
try {
|
|
924
1058
|
return (await lstat(path)).isDirectory();
|
|
@@ -927,10 +1061,15 @@ async function isRealDirectory(path) {
|
|
|
927
1061
|
}
|
|
928
1062
|
}
|
|
929
1063
|
|
|
930
|
-
/**
|
|
1064
|
+
/**
|
|
1065
|
+
* Composable agent names the package root ships (`agents/<name>/agent.md`), or null when it cannot be listed.
|
|
1066
|
+
* @param {string} root
|
|
1067
|
+
* @returns {Promise<Set<string> | null>}
|
|
1068
|
+
*/
|
|
931
1069
|
async function shippedAgentNames(root) {
|
|
932
1070
|
const entries = await listDir(join(root, "agents"));
|
|
933
1071
|
if (entries === null) return null;
|
|
1072
|
+
/** @type {Set<string>} */
|
|
934
1073
|
const names = new Set();
|
|
935
1074
|
for (const entry of entries) {
|
|
936
1075
|
if (await isFile(join(root, "agents", entry, "agent.md"))) names.add(entry);
|
|
@@ -938,10 +1077,15 @@ async function shippedAgentNames(root) {
|
|
|
938
1077
|
return names;
|
|
939
1078
|
}
|
|
940
1079
|
|
|
941
|
-
/**
|
|
1080
|
+
/**
|
|
1081
|
+
* Skill paths the package root ships (`skills/<group>/<name>/SKILL.md`), or null when it cannot be listed.
|
|
1082
|
+
* @param {string} root
|
|
1083
|
+
* @returns {Promise<Set<string> | null>}
|
|
1084
|
+
*/
|
|
942
1085
|
async function shippedSkillPaths(root) {
|
|
943
1086
|
const groups = await listDir(join(root, "skills"));
|
|
944
1087
|
if (groups === null) return null;
|
|
1088
|
+
/** @type {Set<string>} */
|
|
945
1089
|
const paths = new Set();
|
|
946
1090
|
for (const group of groups) {
|
|
947
1091
|
const names = await listDir(join(root, "skills", group));
|
|
@@ -960,12 +1104,15 @@ async function shippedSkillPaths(root) {
|
|
|
960
1104
|
* The two must not collapse into one answer: the sweep records a finished
|
|
961
1105
|
* migration, so a listing failure read as an empty directory would record one
|
|
962
1106
|
* that removed nothing and never retry it.
|
|
1107
|
+
* @param {string} dir
|
|
1108
|
+
* @returns {Promise<string[] | null>}
|
|
963
1109
|
*/
|
|
964
1110
|
async function listDir(dir) {
|
|
965
1111
|
try {
|
|
966
1112
|
return (await readdir(dir)).sort();
|
|
967
1113
|
} catch (err) {
|
|
968
|
-
|
|
1114
|
+
const code = /** @type {NodeJS.ErrnoException} */ (err).code;
|
|
1115
|
+
if (code === "ENOENT" || code === "ENOTDIR") return [];
|
|
969
1116
|
return null;
|
|
970
1117
|
}
|
|
971
1118
|
}
|
|
@@ -978,6 +1125,8 @@ async function listDir(dir) {
|
|
|
978
1125
|
* pi's package rules do the loading: conventional dirs are declared explicitly
|
|
979
1126
|
* so the bundled packages' resources are included alongside ./extensions and
|
|
980
1127
|
* ./skills (one pi-visible source, no tokenised repo copies).
|
|
1128
|
+
* @param {{ root: string, dependencies: PiDependencies }} options
|
|
1129
|
+
* @returns {Promise<PiManifest>}
|
|
981
1130
|
*/
|
|
982
1131
|
async function buildManifest({ root, dependencies }) {
|
|
983
1132
|
const pkg = await readJsonObject(join(root, "package.json"));
|
|
@@ -996,6 +1145,10 @@ async function buildManifest({ root, dependencies }) {
|
|
|
996
1145
|
};
|
|
997
1146
|
}
|
|
998
1147
|
|
|
1148
|
+
/**
|
|
1149
|
+
* @param {{ output: string, manifest: PiManifest }} options
|
|
1150
|
+
* @returns {Promise<{ path: string, manifest: PiManifest }>}
|
|
1151
|
+
*/
|
|
999
1152
|
async function writeManifest({ output, manifest }) {
|
|
1000
1153
|
const path = join(output, "package.json");
|
|
1001
1154
|
await writeFile(path, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
@@ -1006,9 +1159,12 @@ async function writeManifest({ output, manifest }) {
|
|
|
1006
1159
|
* Prove the manifest references real resources before reporting a success: a
|
|
1007
1160
|
* staged package whose declared extension or skill path does not exist would
|
|
1008
1161
|
* load silently short of the tools the agents expect.
|
|
1162
|
+
* @param {{ output: string, manifest: PiManifest }} options
|
|
1163
|
+
* @returns {Promise<void>}
|
|
1009
1164
|
*/
|
|
1010
1165
|
async function verifyManifestResources({ output, manifest }) {
|
|
1011
1166
|
const declared = [...manifest.pi.extensions, ...manifest.pi.skills];
|
|
1167
|
+
/** @type {string[]} */
|
|
1012
1168
|
const missing = [];
|
|
1013
1169
|
for (const rel of declared) {
|
|
1014
1170
|
if (!(await pathExists(join(output, rel)))) missing.push(rel);
|
|
@@ -1024,6 +1180,10 @@ async function verifyManifestResources({ output, manifest }) {
|
|
|
1024
1180
|
* Recursively copy a tree, optionally transforming file contents. `.md` files
|
|
1025
1181
|
* go through `transform` when supplied; everything else is copied verbatim.
|
|
1026
1182
|
* `node_modules` and symlinks are never copied.
|
|
1183
|
+
* @param {string} src
|
|
1184
|
+
* @param {string} dest
|
|
1185
|
+
* @param {{ dryRun?: boolean, transform?: ContentTransform | null }} [options]
|
|
1186
|
+
* @returns {Promise<{ files: number, md: number }>}
|
|
1027
1187
|
*/
|
|
1028
1188
|
async function copyTree(src, dest, { dryRun = false, transform = null } = {}) {
|
|
1029
1189
|
let files = 0;
|
|
@@ -1052,21 +1212,33 @@ async function copyTree(src, dest, { dryRun = false, transform = null } = {}) {
|
|
|
1052
1212
|
|
|
1053
1213
|
// ─── Small helpers ───────────────────────────────────────────────────────────
|
|
1054
1214
|
|
|
1055
|
-
/**
|
|
1215
|
+
/**
|
|
1216
|
+
* `./skills/` → `skills` — a package resource path relative to its own root.
|
|
1217
|
+
* @param {string} entry
|
|
1218
|
+
* @returns {string}
|
|
1219
|
+
*/
|
|
1056
1220
|
function stripRelative(entry) {
|
|
1057
1221
|
return entry.replace(/^\.\//, "").replace(/\/$/, "");
|
|
1058
1222
|
}
|
|
1059
1223
|
|
|
1224
|
+
/**
|
|
1225
|
+
* @param {unknown} value
|
|
1226
|
+
* @returns {string[]}
|
|
1227
|
+
*/
|
|
1060
1228
|
function asStringList(value) {
|
|
1061
1229
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
1062
1230
|
}
|
|
1063
1231
|
|
|
1232
|
+
/**
|
|
1233
|
+
* @param {string} filePath
|
|
1234
|
+
* @returns {Promise<Record<string, any> | null>}
|
|
1235
|
+
*/
|
|
1064
1236
|
async function readJsonObject(filePath) {
|
|
1065
1237
|
let raw;
|
|
1066
1238
|
try {
|
|
1067
1239
|
raw = await readFile(filePath, "utf-8");
|
|
1068
1240
|
} catch (err) {
|
|
1069
|
-
if (err.code === "ENOENT") return null;
|
|
1241
|
+
if (/** @type {NodeJS.ErrnoException} */ (err).code === "ENOENT") return null;
|
|
1070
1242
|
throw err;
|
|
1071
1243
|
}
|
|
1072
1244
|
try {
|
|
@@ -1077,6 +1249,10 @@ async function readJsonObject(filePath) {
|
|
|
1077
1249
|
}
|
|
1078
1250
|
}
|
|
1079
1251
|
|
|
1252
|
+
/**
|
|
1253
|
+
* @param {string} filePath
|
|
1254
|
+
* @returns {Promise<boolean>}
|
|
1255
|
+
*/
|
|
1080
1256
|
async function pathExists(filePath) {
|
|
1081
1257
|
try {
|
|
1082
1258
|
await stat(filePath);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Appends behavioural circuit-breakers to the PI system prompt once per user prompt.
|
|
4
4
|
|
|
5
|
-
This is the first slice of
|
|
5
|
+
This is the first slice of the *Improve PI System Prompt* idea: the highest-leverage
|
|
6
6
|
token reduction is behavioural, not structural. The extension injects a stable block via
|
|
7
7
|
the `before_agent_start` hook — which fires once per user prompt, before the agent loop — so
|
|
8
8
|
the rules apply to every turn of the run without rewriting Pi's default prompt.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Behavioural guardrails for the PI system prompt.
|
|
3
3
|
*
|
|
4
|
-
* The first slice of
|
|
4
|
+
* The first slice of the *Improve PI System Prompt* idea: hard circuit-breakers
|
|
5
5
|
* against wasted retries, explicit check-in triggers, and atomic-change discipline.
|
|
6
6
|
* Kept as a stable constant so it can be appended to every turn's system prompt
|
|
7
7
|
* without invalidating the prompt-cache prefix.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Appends behavioural circuit-breakers to the PI system prompt on every turn:
|
|
5
5
|
* hard retry limits, explicit check-in triggers, and atomic-change discipline
|
|
6
|
-
* (the first slice of
|
|
6
|
+
* (the first slice of the *Improve PI System Prompt* idea).
|
|
7
7
|
*
|
|
8
8
|
* The block is stable text appended after any chained system-prompt changes, so
|
|
9
9
|
* it keeps the prompt-cache prefix intact while remaining authoritative for the
|
|
@@ -98,14 +98,14 @@ describe("discoverAgents (composable source format)", () => {
|
|
|
98
98
|
it("discovers agents from <name>/agent.md + <name>/pi/frontmatter.json", () => {
|
|
99
99
|
const fx = track(makeFixture());
|
|
100
100
|
writeComposableAgent(fx, "ralph", {
|
|
101
|
-
body: "# ralph\nUse {{TOOL:task}} to spawn subagents.\n\n{{SECTION:
|
|
101
|
+
body: "# ralph\nUse {{TOOL:task}} to spawn subagents.\n\n{{SECTION:wake}}",
|
|
102
102
|
frontmatter: {
|
|
103
103
|
name: "agent-cortex:ralph",
|
|
104
104
|
description: "Runs beads end-to-end",
|
|
105
105
|
tools: ["bash", "view", "rg", "glob", "task", "read_agent"],
|
|
106
106
|
},
|
|
107
107
|
sections: {
|
|
108
|
-
|
|
108
|
+
wake: "## PI wake\nPoll with task + read_agent.",
|
|
109
109
|
},
|
|
110
110
|
});
|
|
111
111
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Appends the always-on reply shape to the PI system prompt once per user prompt.
|
|
4
4
|
|
|
5
|
-
This is the second slice of `
|
|
5
|
+
This is the second slice of the "Improve PI System Prompt" idea (bead `agnt-ctx-s5hn`): the first
|
|
6
6
|
(`agent-guardrails`) stops wasted work; this one stops the answer getting buried in
|
|
7
7
|
it. The extension injects a stable block via the `before_agent_start` hook — which
|
|
8
8
|
fires once per user prompt, before the agent loop — so the rules apply to every
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Appends the always-on reply shape to the PI system prompt on every turn: lead
|
|
5
5
|
* with the action, write in Simplified Technical English (ASD-STE100), restate the
|
|
6
|
-
* context the reader cannot remember (the second slice of
|
|
7
|
-
*
|
|
6
|
+
* context the reader cannot remember (the second slice of the "Improve PI
|
|
7
|
+
* System Prompt" idea, bead `agnt-ctx-s5hn`).
|
|
8
8
|
*
|
|
9
9
|
* The block is stable text appended after any chained system-prompt changes, so it
|
|
10
10
|
* keeps the prompt-cache prefix intact while remaining authoritative for the model.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Output style for the PI system prompt.
|
|
3
3
|
*
|
|
4
|
-
* The second slice of
|
|
5
|
-
* the task discipline. Three concerns, in order — lead with the action, write in
|
|
4
|
+
* The second slice of the "Improve PI System Prompt" idea (bead `agnt-ctx-s5hn`): the reply
|
|
5
|
+
* shape, not the task discipline. Three concerns, in order — lead with the action, write in
|
|
6
6
|
* Simplified Technical English, restate the context the reader cannot remember.
|
|
7
7
|
*
|
|
8
8
|
* The prose lives in output-style.md so one file feeds both harnesses: this
|