continuous-improvement 3.12.3 → 3.15.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/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +31 -1
- package/LICENSE +21 -21
- package/QUICKSTART.md +1 -1
- package/README.md +25 -5
- package/bin/analyze.sh +161 -161
- package/bin/backfill.mjs +19 -19
- package/bin/check-doc-runtime-claims.mjs +0 -0
- package/bin/check-docs-substrings.mjs +0 -0
- package/bin/check-everything-mirror.mjs +0 -0
- package/bin/check-routing-targets.mjs +0 -0
- package/bin/check-scripts-citation-drift.mjs +0 -0
- package/bin/check-skill-count.mjs +32 -1
- package/bin/check-skill-law-tag.mjs +0 -0
- package/bin/check-skill-mirror.mjs +0 -0
- package/bin/check-skill-tiers.mjs +0 -0
- package/bin/check-test-imports-only.mjs +1 -1
- package/bin/check-third-party-shape.mjs +0 -0
- package/bin/check-tool-count.mjs +0 -0
- package/bin/companion-preference-status.mjs +0 -0
- package/bin/gateguard-clear.mjs +0 -0
- package/bin/generate-plugin-manifests.mjs +2 -1
- package/bin/harvest-friction.mjs +0 -0
- package/bin/hook-stats.mjs +21 -21
- package/bin/install.mjs +74 -1
- package/bin/lint-transcript.mjs +0 -0
- package/bin/mcp-server.mjs +65 -8
- package/bin/observe.mjs +0 -0
- package/bin/plan-pack.mjs +77 -0
- package/bin/pre-commit-block-strays.sh +0 -0
- package/bin/refresh-third-party.mjs +0 -0
- package/bin/unified-cli.mjs +55 -410
- package/commands/continuous-improvement.md +115 -115
- package/commands/dashboard.md +56 -56
- package/commands/model-forward.md +13 -0
- package/commands/production-readiness-review.md +53 -0
- package/commands/seven-laws.md +16 -16
- package/commands/ship.md +57 -0
- package/commands/workspace-surface-audit.md +77 -77
- package/hooks/companion-preference.mjs +31 -19
- package/hooks/gateguard.mjs +38 -16
- package/hooks/goal-drift-stop.mjs +0 -0
- package/hooks/hook-pack.mjs +110 -0
- package/hooks/observe.sh +0 -0
- package/hooks/recall-briefing.mjs +0 -0
- package/hooks/route-prompt.mjs +0 -0
- package/hooks/session.sh +106 -106
- package/hooks/three-section-close.mjs +0 -0
- package/instinct-packs/go.json +58 -58
- package/instinct-packs/meta.json +16 -16
- package/instinct-packs/python.json +58 -58
- package/instinct-packs/react.json +58 -58
- package/lib/cli-anything.mjs +0 -0
- package/lib/gateguard-state.mjs +62 -13
- package/lib/goal-drift-gate.mjs +0 -0
- package/lib/goal-state.mjs +8 -4
- package/lib/hook-pack-gate.mjs +65 -0
- package/lib/install-targets.mjs +121 -0
- package/lib/observe-event.mjs +0 -0
- package/lib/plan-review-packet.mjs +96 -0
- package/lib/plugin-metadata.mjs +23 -2
- package/lib/pm-marketplace.mjs +0 -0
- package/lib/recall-briefing.mjs +0 -0
- package/lib/recall-index.mjs +0 -0
- package/lib/resolve-home-dir.mjs +0 -0
- package/lib/skill-distill.mjs +141 -0
- package/lib/skill-tiers.mjs +0 -0
- package/llms.txt +2 -2
- package/package.json +4 -3
- package/plugins/beginner.json +2 -2
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +2 -2
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +2 -2
- package/plugins/continuous-improvement/LICENSE +21 -21
- package/plugins/continuous-improvement/README.md +1 -1
- package/plugins/continuous-improvement/bin/backfill.mjs +19 -19
- package/plugins/continuous-improvement/bin/gateguard-clear.mjs +0 -0
- package/plugins/continuous-improvement/bin/mcp-server.mjs +65 -8
- package/plugins/continuous-improvement/bin/observe.mjs +0 -0
- package/plugins/continuous-improvement/commands/continuous-improvement.md +115 -115
- package/plugins/continuous-improvement/commands/dashboard.md +56 -56
- package/plugins/continuous-improvement/commands/model-forward.md +13 -0
- package/plugins/continuous-improvement/commands/production-readiness-review.md +53 -0
- package/plugins/continuous-improvement/commands/seven-laws.md +16 -16
- package/plugins/continuous-improvement/commands/ship.md +57 -0
- package/plugins/continuous-improvement/commands/workspace-surface-audit.md +77 -77
- package/plugins/continuous-improvement/hooks/companion-preference.mjs +31 -19
- package/plugins/continuous-improvement/hooks/gateguard.mjs +38 -16
- package/plugins/continuous-improvement/hooks/goal-drift-stop.mjs +0 -0
- package/plugins/continuous-improvement/hooks/hook-pack.mjs +110 -0
- package/plugins/continuous-improvement/hooks/hooks.json +10 -0
- package/plugins/continuous-improvement/hooks/observe.sh +0 -0
- package/plugins/continuous-improvement/hooks/recall-briefing.mjs +0 -0
- package/plugins/continuous-improvement/hooks/route-prompt.mjs +0 -0
- package/plugins/continuous-improvement/hooks/session.sh +106 -106
- package/plugins/continuous-improvement/hooks/three-section-close.mjs +0 -0
- package/plugins/continuous-improvement/instinct-packs/go.json +58 -58
- package/plugins/continuous-improvement/instinct-packs/meta.json +16 -16
- package/plugins/continuous-improvement/instinct-packs/python.json +58 -58
- package/plugins/continuous-improvement/instinct-packs/react.json +58 -58
- package/plugins/continuous-improvement/lib/gateguard-state.mjs +62 -13
- package/plugins/continuous-improvement/lib/goal-drift-gate.mjs +0 -0
- package/plugins/continuous-improvement/lib/goal-state.mjs +8 -4
- package/plugins/continuous-improvement/lib/hook-pack-gate.mjs +65 -0
- package/plugins/continuous-improvement/lib/observe-event.mjs +0 -0
- package/plugins/continuous-improvement/lib/plugin-metadata.mjs +23 -2
- package/plugins/continuous-improvement/lib/recall-briefing.mjs +0 -0
- package/plugins/continuous-improvement/lib/recall-index.mjs +0 -0
- package/plugins/continuous-improvement/lib/resolve-home-dir.mjs +0 -0
- package/plugins/continuous-improvement/lib/skill-distill.mjs +141 -0
- package/plugins/continuous-improvement/skills/README.md +1 -1
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +4 -4
- package/plugins/continuous-improvement/skills/goal-monitor/SKILL.md +1 -1
- package/plugins/continuous-improvement/skills/handoff/SKILL.md +0 -1
- package/plugins/continuous-improvement/skills/model-forward/SKILL.md +44 -0
- package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -411
- package/plugins/expert.json +5 -1
- package/skills/README.md +4 -2
- package/skills/gateguard.md +4 -4
- package/skills/goal-monitor.md +1 -1
- package/skills/handoff.md +0 -1
- package/skills/model-forward.md +44 -0
- package/skills/tdd-workflow.md +411 -411
- package/lib/compound-engineering.mjs +0 -831
- package/lib/pm-skills.mjs +0 -1274
- package/lib/unified-plugin.mjs +0 -924
- package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +0 -108
- package/skills/para-memory-files.md +0 -108
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - reach-through into `hooks/`, `scripts/`, `src/`, or deeper paths under
|
|
13
13
|
* `bin/` or `lib/`
|
|
14
14
|
*
|
|
15
|
-
* Baseline: all
|
|
15
|
+
* Baseline: all 56 `src/test/*.test.mts` files pass today.
|
|
16
16
|
*
|
|
17
17
|
* Usage:
|
|
18
18
|
* node bin/check-test-imports-only.mjs # Check the current repo
|
|
File without changes
|
package/bin/check-tool-count.mjs
CHANGED
|
File without changes
|
|
File without changes
|
package/bin/gateguard-clear.mjs
CHANGED
|
File without changes
|
|
@@ -107,7 +107,7 @@ async function writePluginBundleReadme() {
|
|
|
107
107
|
"- `tdd-workflow` — RED/GREEN/REFACTOR + 80% coverage gate",
|
|
108
108
|
"- `workspace-surface-audit` — environment + capability audit",
|
|
109
109
|
"- Tier-1/Tier-2 enforcement skills (`gateguard`, `verification-loop`,",
|
|
110
|
-
" `
|
|
110
|
+
" `safety-guard`, `token-budget-advisor`,",
|
|
111
111
|
" `strategic-compact`, `wild-risa-balance`)",
|
|
112
112
|
"",
|
|
113
113
|
"**Optional companions the orchestrator routes to (install separately if",
|
|
@@ -152,6 +152,7 @@ async function writePluginBundle() {
|
|
|
152
152
|
copyFileTo(join(REPO_ROOT, "lib", "observe-event.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "observe-event.mjs")),
|
|
153
153
|
copyFileTo(join(REPO_ROOT, "lib", "goal-state.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "goal-state.mjs")),
|
|
154
154
|
copyFileTo(join(REPO_ROOT, "lib", "goal-drift-gate.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "goal-drift-gate.mjs")),
|
|
155
|
+
copyFileTo(join(REPO_ROOT, "lib", "hook-pack-gate.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "hook-pack-gate.mjs")),
|
|
155
156
|
copyFileTo(join(REPO_ROOT, "lib", "recall-index.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "recall-index.mjs")),
|
|
156
157
|
copyFileTo(join(REPO_ROOT, "lib", "recall-briefing.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "recall-briefing.mjs")),
|
|
157
158
|
copyFileTo(join(REPO_ROOT, "lib", "skill-distill.mjs"), join(PLUGIN_BUNDLE_DIR, "lib", "skill-distill.mjs")),
|
package/bin/harvest-friction.mjs
CHANGED
|
File without changes
|
package/bin/hook-stats.mjs
CHANGED
|
@@ -46,27 +46,27 @@ import { platform } from "node:os";
|
|
|
46
46
|
import { join } from "node:path";
|
|
47
47
|
import { argv, cwd, exit, stderr, stdout } from "node:process";
|
|
48
48
|
import { resolveHomeDir } from "../lib/resolve-home-dir.mjs";
|
|
49
|
-
const USAGE = `Usage: node bin/hook-stats.mjs [options]
|
|
50
|
-
|
|
51
|
-
Reads JSONL telemetry from <HOME>/.claude/hook-telemetry/*.jsonl and
|
|
52
|
-
prints per-hook aggregate counts over a recent time window.
|
|
53
|
-
|
|
54
|
-
Options:
|
|
55
|
-
--hours=<n> Time window in hours (default: 24)
|
|
56
|
-
--hash=<hash> Only read this single <hash>.jsonl file
|
|
57
|
-
(default: sha256 of the current working directory,
|
|
58
|
-
normalized to forward slashes and lowercased on
|
|
59
|
-
Windows, then first 12 hex chars)
|
|
60
|
-
--telemetry-dir=<path> Override telemetry dir
|
|
61
|
-
(default: <HOME>/.claude/hook-telemetry)
|
|
62
|
-
--help Show this help and exit 0
|
|
63
|
-
|
|
64
|
-
Output:
|
|
65
|
-
One line per hook: "<hook> last <hours>h: pass=<n> block=<n>
|
|
66
|
-
skip-short=<n> total=<n> avg-duration=<X>ms".
|
|
67
|
-
If there are no records in the window: "no records in last <hours>h".
|
|
68
|
-
|
|
69
|
-
The reader is parse-tolerant: malformed JSONL lines are skipped silently.
|
|
49
|
+
const USAGE = `Usage: node bin/hook-stats.mjs [options]
|
|
50
|
+
|
|
51
|
+
Reads JSONL telemetry from <HOME>/.claude/hook-telemetry/*.jsonl and
|
|
52
|
+
prints per-hook aggregate counts over a recent time window.
|
|
53
|
+
|
|
54
|
+
Options:
|
|
55
|
+
--hours=<n> Time window in hours (default: 24)
|
|
56
|
+
--hash=<hash> Only read this single <hash>.jsonl file
|
|
57
|
+
(default: sha256 of the current working directory,
|
|
58
|
+
normalized to forward slashes and lowercased on
|
|
59
|
+
Windows, then first 12 hex chars)
|
|
60
|
+
--telemetry-dir=<path> Override telemetry dir
|
|
61
|
+
(default: <HOME>/.claude/hook-telemetry)
|
|
62
|
+
--help Show this help and exit 0
|
|
63
|
+
|
|
64
|
+
Output:
|
|
65
|
+
One line per hook: "<hook> last <hours>h: pass=<n> block=<n>
|
|
66
|
+
skip-short=<n> total=<n> avg-duration=<X>ms".
|
|
67
|
+
If there are no records in the window: "no records in last <hours>h".
|
|
68
|
+
|
|
69
|
+
The reader is parse-tolerant: malformed JSONL lines are skipped silently.
|
|
70
70
|
`;
|
|
71
71
|
function parseArgs(rawArgs) {
|
|
72
72
|
const opts = {
|
package/bin/install.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* npx continuous-improvement install # beginner mode (default)
|
|
7
7
|
* npx continuous-improvement install --mode expert # + MCP server + session hooks
|
|
8
8
|
* npx continuous-improvement install --pack react # load starter instinct pack
|
|
9
|
+
* npx continuous-improvement install --target gemini,codex # skill into other agents' rules files
|
|
9
10
|
* npx continuous-improvement install --uninstall # remove everything
|
|
10
11
|
*/
|
|
11
12
|
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
@@ -15,6 +16,7 @@ import { dirname, join } from "node:path";
|
|
|
15
16
|
import { fileURLToPath } from "node:url";
|
|
16
17
|
import { createHash } from "node:crypto";
|
|
17
18
|
import { getToolNames } from "../lib/plugin-metadata.mjs";
|
|
19
|
+
import { TARGET_IDS, planTargetWrites, resolveTargets } from "../lib/install-targets.mjs";
|
|
18
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
21
|
const __dirname = dirname(__filename);
|
|
20
22
|
const SKILL_SOURCE = join(__dirname, "..", "SKILL.md");
|
|
@@ -493,6 +495,17 @@ Options for 'install':
|
|
|
493
495
|
beginner — hooks + skill + commands (default)
|
|
494
496
|
expert — beginner + MCP server + session hooks
|
|
495
497
|
--pack <name> Load a starter instinct pack (react, python, go, meta)
|
|
498
|
+
--target <names> Comma-separated platform list (default: claude):
|
|
499
|
+
claude — full install: hooks + skill + commands
|
|
500
|
+
gemini — GEMINI.md (Gemini CLI)
|
|
501
|
+
codex — AGENTS.md (Codex CLI / agents.md standard)
|
|
502
|
+
cursor — .cursor/rules/continuous-improvement.mdc
|
|
503
|
+
windsurf — .windsurf/rules/continuous-improvement.md
|
|
504
|
+
zed — .rules
|
|
505
|
+
aider — CONVENTIONS.md + .aider.conf.yml
|
|
506
|
+
copilot — .github/copilot-instructions.md
|
|
507
|
+
Non-claude targets write the 7-Laws skill text into the
|
|
508
|
+
current project; hooks/MCP/instincts stay Claude Code-only.
|
|
496
509
|
--help Show this help
|
|
497
510
|
|
|
498
511
|
Options for 'backfill':
|
|
@@ -503,6 +516,7 @@ Examples:
|
|
|
503
516
|
npx continuous-improvement install # beginner (default)
|
|
504
517
|
npx continuous-improvement install --mode expert # full power
|
|
505
518
|
npx continuous-improvement install --pack react # load React instincts
|
|
519
|
+
npx continuous-improvement install --target gemini,codex # other agents' rules files
|
|
506
520
|
npx continuous-improvement backfill --dry-run # report thin/rich row counts
|
|
507
521
|
npx continuous-improvement backfill # tag rows in place
|
|
508
522
|
npx continuous-improvement install --uninstall # remove everything
|
|
@@ -510,7 +524,12 @@ Examples:
|
|
|
510
524
|
}
|
|
511
525
|
function getProjectHashSync() {
|
|
512
526
|
try {
|
|
513
|
-
|
|
527
|
+
// No shell redirect — `2>/dev/null` breaks under cmd.exe (tries to open a
|
|
528
|
+
// file literally named /dev/null); ignore stderr via stdio instead.
|
|
529
|
+
const root = execSync("git rev-parse --show-toplevel", {
|
|
530
|
+
encoding: "utf8",
|
|
531
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
532
|
+
}).trim();
|
|
514
533
|
const hash = createHash("sha256").update(root).digest("hex").slice(0, 12);
|
|
515
534
|
return { root, hash };
|
|
516
535
|
}
|
|
@@ -547,6 +566,60 @@ if (args.includes("--uninstall")) {
|
|
|
547
566
|
uninstallAll();
|
|
548
567
|
process.exit(0);
|
|
549
568
|
}
|
|
569
|
+
// --target <names>: comma-separated platform list, default claude-only.
|
|
570
|
+
// Non-claude targets receive the skill text in their platform's rules file
|
|
571
|
+
// (project-level); the full hook/MCP/instinct install stays Claude Code-only.
|
|
572
|
+
const targetFlagIndex = rawArgs.indexOf("--target");
|
|
573
|
+
let requestedTargets = ["claude"];
|
|
574
|
+
if (targetFlagIndex !== -1) {
|
|
575
|
+
const targetCsv = rawArgs[targetFlagIndex + 1];
|
|
576
|
+
if (!targetCsv || targetCsv.startsWith("--")) {
|
|
577
|
+
console.error(`--target requires a value. Valid targets: ${TARGET_IDS.join(", ")}`);
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
const resolved = resolveTargets(targetCsv);
|
|
581
|
+
if (resolved.unknown.length > 0) {
|
|
582
|
+
console.error(`Unknown --target name(s): ${resolved.unknown.join(", ")}. Valid targets: ${TARGET_IDS.join(", ")}`);
|
|
583
|
+
process.exit(1);
|
|
584
|
+
}
|
|
585
|
+
if (resolved.targets.length === 0) {
|
|
586
|
+
console.error(`--target requires at least one name. Valid targets: ${TARGET_IDS.join(", ")}`);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
requestedTargets = resolved.targets;
|
|
590
|
+
}
|
|
591
|
+
function installNonClaudeTargets(targetIds) {
|
|
592
|
+
const skillMd = readFileSync(SKILL_SOURCE, "utf8");
|
|
593
|
+
const project = getProjectHashSync();
|
|
594
|
+
const projectRoot = project.root === "global" ? process.cwd() : project.root;
|
|
595
|
+
const readExisting = (relPath) => {
|
|
596
|
+
const absPath = join(projectRoot, relPath);
|
|
597
|
+
return existsSync(absPath) ? readFileSync(absPath, "utf8") : null;
|
|
598
|
+
};
|
|
599
|
+
const notes = new Set();
|
|
600
|
+
for (const targetId of targetIds) {
|
|
601
|
+
const plan = planTargetWrites(targetId, skillMd, readExisting);
|
|
602
|
+
for (const write of plan.writes) {
|
|
603
|
+
const absPath = join(projectRoot, write.relPath);
|
|
604
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
605
|
+
writeFileSync(absPath, write.content);
|
|
606
|
+
console.log(` ✓ ${targetId} → ${write.relPath}`);
|
|
607
|
+
}
|
|
608
|
+
for (const note of plan.notes)
|
|
609
|
+
notes.add(note);
|
|
610
|
+
}
|
|
611
|
+
for (const note of notes)
|
|
612
|
+
console.log(` ℹ ${note}`);
|
|
613
|
+
}
|
|
614
|
+
const nonClaudeTargets = requestedTargets.filter((targetId) => targetId !== "claude");
|
|
615
|
+
if (nonClaudeTargets.length > 0) {
|
|
616
|
+
console.log("\ncontinuous-improvement multi-platform install\n");
|
|
617
|
+
installNonClaudeTargets(nonClaudeTargets);
|
|
618
|
+
if (!requestedTargets.includes("claude")) {
|
|
619
|
+
console.log("\nDone. Claude Code install skipped (not in --target list).");
|
|
620
|
+
process.exit(0);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
550
623
|
console.log(`
|
|
551
624
|
continuous-improvement (mode: ${INSTALL_MODE})
|
|
552
625
|
Research → Plan → Execute → Verify → Reflect → Learn → Iterate
|
package/bin/lint-transcript.mjs
CHANGED
|
File without changes
|
package/bin/mcp-server.mjs
CHANGED
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
import { execSync } from "node:child_process";
|
|
14
14
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
15
15
|
import { homedir } from "node:os";
|
|
16
|
-
import { basename, dirname, join } from "node:path";
|
|
16
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
17
17
|
import { createInterface } from "node:readline";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import { createHash } from "node:crypto";
|
|
20
20
|
import { PACKAGE_NAME, VERSION, getToolDefinitions, isPluginMode, } from "../lib/plugin-metadata.mjs";
|
|
21
21
|
import { formatDriftReport, parseGoalFromPlan, scoreObservations, } from "../lib/goal-state.mjs";
|
|
22
22
|
import { buildIndex, formatRecallHits, parseSince, query as queryRecall, } from "../lib/recall-index.mjs";
|
|
23
|
-
import { draftFromCandidate, extractTrajectories, findCandidates, formatCandidates, serializeDraft, } from "../lib/skill-distill.mjs";
|
|
24
|
-
import { MAX_CLEARED_FILES, clearFiles, resolveSessionDir, } from "../lib/gateguard-state.mjs";
|
|
23
|
+
import { draftFromCandidate, draftFromWorkflowRun, extractTrajectories, findCandidates, formatCandidates, serializeDraft, workflowRunFromObservations, } from "../lib/skill-distill.mjs";
|
|
24
|
+
import { MAX_CLEARED_FILES, canonicalizeFileKey, clearFiles, resolveInstinctsRoot, resolveSessionDir, } from "../lib/gateguard-state.mjs";
|
|
25
25
|
function getHomeDir() {
|
|
26
26
|
return process.env.HOME || process.env.USERPROFILE || homedir();
|
|
27
27
|
}
|
|
@@ -508,10 +508,11 @@ function handleTool(name, params) {
|
|
|
508
508
|
}
|
|
509
509
|
case "ci_gateguard_clear": {
|
|
510
510
|
// Beginner-available on purpose: the GateGuard hook fires for every
|
|
511
|
-
// install, so the clearance action must too.
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
511
|
+
// install, so the clearance action must too. The hook's block reason now
|
|
512
|
+
// prints state_path (the session-scoped state file); honoring it writes
|
|
513
|
+
// the marker exactly where the hook looks. Without it the MCP server can't
|
|
514
|
+
// know the caller's session, so it falls back to the canonical session dir
|
|
515
|
+
// — correct only on the legacy unscoped path.
|
|
515
516
|
const rawList = Array.isArray(params.file_paths) ? params.file_paths : [];
|
|
516
517
|
const listPaths = rawList.filter((value) => typeof value === "string" && value.length > 0);
|
|
517
518
|
const single = getString(params.file_path).trim();
|
|
@@ -519,7 +520,24 @@ function handleTool(name, params) {
|
|
|
519
520
|
if (paths.length === 0) {
|
|
520
521
|
return error("file_paths is required — pass the file path(s) named in the GateGuard block reason, e.g. { file_paths: [\"src/x.ts\"] }.");
|
|
521
522
|
}
|
|
522
|
-
|
|
523
|
+
// state_path is a model-controlled argument, so it could be a traversal
|
|
524
|
+
// string (`../../etc/...`). dirname leaves `..` segments for the OS to
|
|
525
|
+
// resolve at write time, so without a bound this is an arbitrary-write
|
|
526
|
+
// primitive. Resolve + canonicalize, then require containment within the
|
|
527
|
+
// instincts root (the only tree the hook ever prints a state_path inside).
|
|
528
|
+
const rawStatePath = getString(params.state_path).trim();
|
|
529
|
+
let sessionDir;
|
|
530
|
+
if (rawStatePath) {
|
|
531
|
+
const resolvedKey = canonicalizeFileKey(resolve(rawStatePath));
|
|
532
|
+
const rootKey = canonicalizeFileKey(resolveInstinctsRoot());
|
|
533
|
+
if (resolvedKey !== rootKey && !resolvedKey.startsWith(`${rootKey}/`)) {
|
|
534
|
+
return error("state_path must resolve inside ~/.claude/instincts/. Pass it verbatim from the GateGuard block reason.");
|
|
535
|
+
}
|
|
536
|
+
sessionDir = dirname(resolve(rawStatePath));
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
sessionDir = resolveSessionDir();
|
|
540
|
+
}
|
|
523
541
|
const { cleared, skippedForCap } = clearFiles(sessionDir, paths);
|
|
524
542
|
const lines = [
|
|
525
543
|
"## GateGuard clearance",
|
|
@@ -837,6 +855,45 @@ function handleTool(name, params) {
|
|
|
837
855
|
"```",
|
|
838
856
|
].join("\n"));
|
|
839
857
|
}
|
|
858
|
+
case "ci_distill_from_workflow": {
|
|
859
|
+
if (MODE !== "expert") {
|
|
860
|
+
return error("ci_distill_from_workflow requires expert mode");
|
|
861
|
+
}
|
|
862
|
+
const run = workflowRunFromObservations(readDistillObservations(project.hash));
|
|
863
|
+
if (!run) {
|
|
864
|
+
return text("No completed-and-verified Workflow run found in this project's observations. " +
|
|
865
|
+
"A run qualifies when a `Workflow` tool call is followed by a passing verify/test/build in the same feed. " +
|
|
866
|
+
"Run a workflow, verify its output, then try `ci_distill_from_workflow` again.");
|
|
867
|
+
}
|
|
868
|
+
const draftInstinct = draftFromWorkflowRun(run);
|
|
869
|
+
if (!isSafeDraftId(draftInstinct.id)) {
|
|
870
|
+
return error(`Internal error: generated draft id "${draftInstinct.id}" failed the safety check.`);
|
|
871
|
+
}
|
|
872
|
+
const draft = serializeDraft(draftInstinct);
|
|
873
|
+
const draftsDir = join(INSTINCTS_DIR, project.hash, "drafts");
|
|
874
|
+
const draftPath = join(draftsDir, `${draftInstinct.id}.yaml`);
|
|
875
|
+
try {
|
|
876
|
+
mkdirSync(draftsDir, { recursive: true });
|
|
877
|
+
writeFileSync(draftPath, draft);
|
|
878
|
+
}
|
|
879
|
+
catch (err) {
|
|
880
|
+
return error(`Failed to write draft to ${draftPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
881
|
+
}
|
|
882
|
+
return text([
|
|
883
|
+
"## Draft written from a verified workflow run",
|
|
884
|
+
"",
|
|
885
|
+
`**Workflow:** ${run.name}`,
|
|
886
|
+
`**Path:** ${draftPath}`,
|
|
887
|
+
"",
|
|
888
|
+
"Edit the body to capture the real recipe (preconditions, concrete steps, gotchas), then promote with:",
|
|
889
|
+
"",
|
|
890
|
+
` ci_distill_promote id=${draftInstinct.id}`,
|
|
891
|
+
"",
|
|
892
|
+
"```yaml",
|
|
893
|
+
draft.trimEnd(),
|
|
894
|
+
"```",
|
|
895
|
+
].join("\n"));
|
|
896
|
+
}
|
|
840
897
|
case "ci_distill_promote": {
|
|
841
898
|
if (MODE !== "expert") {
|
|
842
899
|
return error("ci_distill_promote requires expert mode");
|
package/bin/observe.mjs
CHANGED
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* plan-pack (`ci-plan-pack`).
|
|
4
|
+
*
|
|
5
|
+
* Turn a plan doc into a commentable review packet a colleague can mark up.
|
|
6
|
+
*
|
|
7
|
+
* node bin/plan-pack.mjs <plan.md> write <plan>.review.md
|
|
8
|
+
* node bin/plan-pack.mjs <plan.md> --stdout print the packet to stdout
|
|
9
|
+
* node bin/plan-pack.mjs <plan.md> --gh-issue write the packet, then print a
|
|
10
|
+
* ready-to-run `gh issue create`
|
|
11
|
+
* command (does NOT auto-post)
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { extname } from "node:path";
|
|
15
|
+
import { pathToFileURL } from "node:url";
|
|
16
|
+
import { buildReviewPacket } from "../lib/plan-review-packet.mjs";
|
|
17
|
+
function getErrorMessage(error) {
|
|
18
|
+
return error instanceof Error ? error.message : String(error);
|
|
19
|
+
}
|
|
20
|
+
function reviewPathFor(source) {
|
|
21
|
+
const ext = extname(source);
|
|
22
|
+
return ext ? `${source.slice(0, -ext.length)}.review${ext}` : `${source}.review.md`;
|
|
23
|
+
}
|
|
24
|
+
function packetTitle(packet) {
|
|
25
|
+
const match = /^# Review packet: (.*)$/m.exec(packet);
|
|
26
|
+
return match ? match[1].trim() : "Plan review";
|
|
27
|
+
}
|
|
28
|
+
const HELP = `
|
|
29
|
+
plan-pack (ci-plan-pack) - turn a plan doc into a commentable review packet
|
|
30
|
+
|
|
31
|
+
Usage: ci-plan-pack <plan.md> [--stdout] [--gh-issue]
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
--stdout Print the packet to stdout instead of writing a file
|
|
35
|
+
--gh-issue Write the packet, then print a 'gh issue create' command
|
|
36
|
+
(does not post anything - you run it)
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
ci-plan-pack docs/plans/2026-06-15-plan-pack.md
|
|
40
|
+
ci-plan-pack docs/plans/2026-06-15-plan-pack.md --stdout
|
|
41
|
+
`;
|
|
42
|
+
function main() {
|
|
43
|
+
const args = process.argv.slice(2);
|
|
44
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
45
|
+
console.log(HELP);
|
|
46
|
+
process.exit(args.length === 0 ? 1 : 0);
|
|
47
|
+
}
|
|
48
|
+
const toStdout = args.includes("--stdout");
|
|
49
|
+
const ghIssue = args.includes("--gh-issue");
|
|
50
|
+
const source = args.find((a) => !a.startsWith("-"));
|
|
51
|
+
if (!source) {
|
|
52
|
+
throw new Error("Usage: ci-plan-pack <plan.md> [--stdout] [--gh-issue]");
|
|
53
|
+
}
|
|
54
|
+
const markdown = readFileSync(source, "utf8");
|
|
55
|
+
const packet = buildReviewPacket(markdown, { source });
|
|
56
|
+
if (toStdout) {
|
|
57
|
+
process.stdout.write(packet.endsWith("\n") ? packet : `${packet}\n`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const outPath = reviewPathFor(source);
|
|
61
|
+
writeFileSync(outPath, packet.endsWith("\n") ? packet : `${packet}\n`);
|
|
62
|
+
console.log(`Wrote review packet: ${outPath}`);
|
|
63
|
+
if (ghIssue) {
|
|
64
|
+
const title = packetTitle(packet);
|
|
65
|
+
console.log("\nTo open it for comments on GitHub, run:");
|
|
66
|
+
console.log(` gh issue create --title "${title}" --body-file "${outPath}"`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
70
|
+
try {
|
|
71
|
+
main();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error(`Error: ${getErrorMessage(error)}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
File without changes
|
|
File without changes
|