continuous-improvement 3.12.3 → 3.16.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 +45 -1
- package/LICENSE +21 -21
- package/QUICKSTART.md +1 -1
- package/README.md +29 -8
- 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-prose.mjs +168 -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 +120 -2
- package/bin/lint-transcript.mjs +15 -3
- package/bin/mcp-server.mjs +66 -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/roast.md +34 -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/hooks/workflow-distill.mjs +145 -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 +30 -4
- 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/lib/version-check.mjs +115 -0
- package/llms.txt +2 -2
- package/package.json +6 -4
- 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 +66 -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/roast.md +34 -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 +16 -1
- 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/hooks/workflow-distill.mjs +145 -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 +30 -4
- 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 +2 -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/roast/SKILL.md +108 -0
- package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +12 -32
- package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -411
- package/plugins/expert.json +5 -1
- package/skills/README.md +5 -3
- 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/roast.md +108 -0
- package/skills/strategic-compact.md +12 -32
- 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
package/lib/skill-distill.mjs
CHANGED
|
@@ -220,3 +220,144 @@ export function formatCandidates(candidates, limit = 10) {
|
|
|
220
220
|
}
|
|
221
221
|
return lines.join("\n");
|
|
222
222
|
}
|
|
223
|
+
// ── Workflow-run → instinct bridge ───────────────────────────────────────────
|
|
224
|
+
// A native Workflow run (Opus 4.8 orchestration / ultracode) is recorded in the
|
|
225
|
+
// observation feed as a `tool: "Workflow"` row whose input_summary holds
|
|
226
|
+
// {"script":"..."} — truncated (~500 chars), but meta.name/description/phases sit
|
|
227
|
+
// at the head of the script and survive. The output_summary holds
|
|
228
|
+
// {"status","runId",...} with status "async_launched": the feed captures the
|
|
229
|
+
// LAUNCH, not the result. So a workflow's success is never read from the Workflow
|
|
230
|
+
// row itself — it is inferred from a following verify-exit-0 in the same feed.
|
|
231
|
+
//
|
|
232
|
+
// Unlike findCandidates (which needs a pattern recurring across >=2 sessions to
|
|
233
|
+
// reject coincidence), a Workflow script is an AUTHORED recipe: one verified run
|
|
234
|
+
// warrants a draft, so the session/occurrence thresholds do not apply here.
|
|
235
|
+
const WORKFLOW_TOOL = "Workflow";
|
|
236
|
+
// Pull meta.name / meta.description / meta.phases[].title out of a (possibly
|
|
237
|
+
// truncated) workflow script embedded as JSON in the observation input_summary.
|
|
238
|
+
// Fail-closed: returns null unless a name is recoverable — a recipe with no
|
|
239
|
+
// identity is never fabricated.
|
|
240
|
+
function parseWorkflowScript(inputSummary) {
|
|
241
|
+
if (!inputSummary)
|
|
242
|
+
return null;
|
|
243
|
+
let script = "";
|
|
244
|
+
try {
|
|
245
|
+
const parsed = JSON.parse(inputSummary);
|
|
246
|
+
if (typeof parsed.script === "string")
|
|
247
|
+
script = parsed.script;
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// input_summary may itself be truncated mid-JSON; recover the script field loosely.
|
|
251
|
+
const m = inputSummary.match(/"script"\s*:\s*"((?:[^"\\]|\\.)*)/);
|
|
252
|
+
if (m) {
|
|
253
|
+
try {
|
|
254
|
+
script = JSON.parse(`"${m[1]}"`);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
script = m[1].replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\'/g, "'");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (!script)
|
|
262
|
+
return null;
|
|
263
|
+
// Scope name/description to the meta head (text before `phases:`) so a phase
|
|
264
|
+
// object's own description: is never mistaken for meta.description; scope phase
|
|
265
|
+
// titles to the phases array literal so inline agent/step title: fields are not
|
|
266
|
+
// captured. The capture classes exclude quotes and newlines, so a hostile
|
|
267
|
+
// name/description cannot inject lines into the draft YAML — serializeDraft emits
|
|
268
|
+
// trigger as a quoted scalar.
|
|
269
|
+
const phasesAt = script.search(/\bphases\s*:/);
|
|
270
|
+
const metaHead = phasesAt >= 0 ? script.slice(0, phasesAt) : script;
|
|
271
|
+
const name = (metaHead.match(/\bname\s*:\s*['"]([^'"\r\n]+)['"]/) ?? [])[1] ?? "";
|
|
272
|
+
if (!name)
|
|
273
|
+
return null; // fail closed: no recipe identity
|
|
274
|
+
const description = (metaHead.match(/\bdescription\s*:\s*['"]([^'"\r\n]+)['"]/) ?? [])[1] ?? "";
|
|
275
|
+
const phasesBlock = (script.match(/\bphases\s*:\s*\[([^\]]*)\]/) ?? [])[1] ?? "";
|
|
276
|
+
const phases = [];
|
|
277
|
+
const phaseRe = /\btitle\s*:\s*['"]([^'"\r\n]+)['"]/g;
|
|
278
|
+
let pm;
|
|
279
|
+
while ((pm = phaseRe.exec(phasesBlock)) !== null)
|
|
280
|
+
phases.push(pm[1]);
|
|
281
|
+
return { name, description, phases };
|
|
282
|
+
}
|
|
283
|
+
// A single verify-exit-0 Bash row: a verify/test/build command whose output is not
|
|
284
|
+
// failing. Mirrors classifyTrajectorySuccess's verify branch for one observation.
|
|
285
|
+
function isVerifySuccessRow(observation) {
|
|
286
|
+
if ((observation.tool ?? "") !== "Bash")
|
|
287
|
+
return false;
|
|
288
|
+
const input = (observation.input_summary ?? "").toString();
|
|
289
|
+
const output = (observation.output_summary ?? "").toString();
|
|
290
|
+
return VERIFY_CMD.test(input) && !FAILURE_MARKER.test(output) && (output === "" || SUCCESS_MARKER.test(output));
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Detect the most recent completed-and-verified Workflow run in an observation
|
|
294
|
+
* list. Returns null (fail closed) unless: a `tool: "Workflow"` row carries a
|
|
295
|
+
* parseable script with a name, AND a verify-exit-0 row follows it in the feed.
|
|
296
|
+
* The trailing verify is the only success signal — the Workflow row records the
|
|
297
|
+
* launch, never the result.
|
|
298
|
+
*/
|
|
299
|
+
export function workflowRunFromObservations(observations) {
|
|
300
|
+
let wfIndex = -1;
|
|
301
|
+
let meta = null;
|
|
302
|
+
for (let i = observations.length - 1; i >= 0; i -= 1) {
|
|
303
|
+
if ((observations[i].tool ?? "") !== WORKFLOW_TOOL)
|
|
304
|
+
continue;
|
|
305
|
+
const parsed = parseWorkflowScript((observations[i].input_summary ?? "").toString());
|
|
306
|
+
if (parsed) {
|
|
307
|
+
wfIndex = i;
|
|
308
|
+
meta = parsed;
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (wfIndex === -1 || !meta)
|
|
313
|
+
return null;
|
|
314
|
+
const wfSession = (observations[wfIndex].session ?? "").toString();
|
|
315
|
+
// The verify that proves the run must follow the Workflow row in the SAME session.
|
|
316
|
+
// A verify from an unrelated later task (different session) does not count — the
|
|
317
|
+
// run is asynchronous, so an interleaved verify could otherwise falsely prove it.
|
|
318
|
+
let verifyCommand = "";
|
|
319
|
+
for (let i = wfIndex + 1; i < observations.length; i += 1) {
|
|
320
|
+
const row = observations[i];
|
|
321
|
+
if ((row.session ?? "").toString() !== wfSession)
|
|
322
|
+
continue;
|
|
323
|
+
if (isVerifySuccessRow(row)) {
|
|
324
|
+
verifyCommand = (row.input_summary ?? "").toString();
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (!verifyCommand)
|
|
329
|
+
return null; // fail closed: no evidence the run's output landed
|
|
330
|
+
return { name: meta.name, description: meta.description, phases: meta.phases, verifyCommand };
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Turn a verified Workflow run into a DRAFT instinct. The script's phase outline is
|
|
334
|
+
* a real skeleton (not a placeholder n-gram), but the human still edits the body
|
|
335
|
+
* before promoting. Reuses serializeDraft and the drafts/ ladder; the
|
|
336
|
+
* `draft-workflow-` id prefix marks the source and stays filesystem-safe.
|
|
337
|
+
*/
|
|
338
|
+
export function draftFromWorkflowRun(run) {
|
|
339
|
+
// Cap the slug so a hostile/huge meta.name cannot produce a path that trips
|
|
340
|
+
// ENAMETOOLONG on write; strip a trailing hyphen left by the cut.
|
|
341
|
+
const slug = slugifyNgram([run.name]).slice(0, 120).replace(/-+$/, "") || "workflow";
|
|
342
|
+
const phaseLine = run.phases.length > 0 ? run.phases.join(" → ") : "(phases not captured)";
|
|
343
|
+
const lines = [
|
|
344
|
+
`When this situation recurs, the workflow "${run.name}" handled it end to end and its output passed verification.`,
|
|
345
|
+
"",
|
|
346
|
+
];
|
|
347
|
+
if (run.description)
|
|
348
|
+
lines.push(`Intent: ${run.description}`);
|
|
349
|
+
lines.push(`Phases: ${phaseLine}`);
|
|
350
|
+
lines.push(`Verified by: ${run.verifyCommand}`);
|
|
351
|
+
lines.push("", "Replace this with the concrete steps, preconditions, and gotchas before promoting —", "the phase outline is the skeleton, not the full recipe.");
|
|
352
|
+
return {
|
|
353
|
+
id: `draft-workflow-${slug}`,
|
|
354
|
+
trigger: `Auto-detected from a verified workflow run: ${run.name}${run.description ? ` — ${run.description}` : ""}`,
|
|
355
|
+
body: lines.join("\n"),
|
|
356
|
+
confidence: DRAFT_CONFIDENCE,
|
|
357
|
+
domain: "workflow",
|
|
358
|
+
ngram: run.phases.length > 0 ? run.phases : [run.name],
|
|
359
|
+
occurrences: 1,
|
|
360
|
+
sessions: 1,
|
|
361
|
+
outcome: "workflow-verified",
|
|
362
|
+
};
|
|
363
|
+
}
|
package/lib/skill-tiers.mjs
CHANGED
|
File without changes
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// version-check.mts — Pure update-check decision core + a thin npm-registry fetch
|
|
2
|
+
// helper. The decision functions take no ambient I/O: the caller injects the local
|
|
3
|
+
// version, the fetched remote version, the cached state, and the clock, so the whole
|
|
4
|
+
// thing is unit-testable offline (mirrors how the goal-state / recall scorers are
|
|
5
|
+
// structured). The ONLY network is fetchLatestNpmVersion, which fails closed
|
|
6
|
+
// (returns null) on any error. No telemetry: a one-way read of the public registry.
|
|
7
|
+
//
|
|
8
|
+
// Wired into bin/install.mts at install end so the nudge only appears when the user
|
|
9
|
+
// explicitly invokes the CLI. See docs/plans/2026-06-25-version-check-nudge.md.
|
|
10
|
+
// Two-tier TTL, copied from gstack: poll often while up-to-date so a release is
|
|
11
|
+
// caught quickly, then back off once an upgrade is known so the nudge is not
|
|
12
|
+
// re-fetched on every invocation.
|
|
13
|
+
export const TTL_UP_TO_DATE_MS = 60 * 60 * 1000; // 60 min
|
|
14
|
+
export const TTL_UPGRADE_MS = 12 * 60 * 60 * 1000; // 720 min
|
|
15
|
+
const NPM_REGISTRY = "https://registry.npmjs.org";
|
|
16
|
+
/** Parse "a.b.c" (ignoring a leading v and any -prerelease/+build suffix) into a
|
|
17
|
+
* numeric triple. Fail closed: any non-numeric core component returns null. */
|
|
18
|
+
export function parseSemver(version) {
|
|
19
|
+
if (typeof version !== "string")
|
|
20
|
+
return null;
|
|
21
|
+
const core = version.trim().replace(/^v/, "").split(/[-+]/)[0] ?? "";
|
|
22
|
+
const parts = core.split(".");
|
|
23
|
+
if (parts.length !== 3)
|
|
24
|
+
return null;
|
|
25
|
+
const nums = parts.map((p) => (/^\d+$/.test(p) ? Number(p) : NaN));
|
|
26
|
+
if (nums.some((n) => !Number.isFinite(n)))
|
|
27
|
+
return null;
|
|
28
|
+
return [nums[0], nums[1], nums[2]];
|
|
29
|
+
}
|
|
30
|
+
/** Is `remote` a strictly higher release than `local`? Non-semver on either side
|
|
31
|
+
* fails closed to false (never nudge on garbage; never nudge a dev build that is
|
|
32
|
+
* ahead of or equal to the registry). */
|
|
33
|
+
export function isNewer(remote, local) {
|
|
34
|
+
const r = parseSemver(remote);
|
|
35
|
+
const l = parseSemver(local);
|
|
36
|
+
if (!r || !l)
|
|
37
|
+
return false;
|
|
38
|
+
for (let i = 0; i < 3; i += 1) {
|
|
39
|
+
if (r[i] > l[i])
|
|
40
|
+
return true;
|
|
41
|
+
if (r[i] < l[i])
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
/** Should the network fetch be skipped because the cached result is still fresh?
|
|
47
|
+
* Absent/NaN checkedAt fails OPEN (fetch) — a corrupt cache must not wedge the
|
|
48
|
+
* check off permanently. */
|
|
49
|
+
export function isThrottled(cache, now) {
|
|
50
|
+
if (!cache || typeof cache.checkedAt !== "number" || !Number.isFinite(cache.checkedAt))
|
|
51
|
+
return false;
|
|
52
|
+
const ttl = cache.status === "upgrade-available" ? TTL_UPGRADE_MS : TTL_UP_TO_DATE_MS;
|
|
53
|
+
return now - cache.checkedAt < ttl;
|
|
54
|
+
}
|
|
55
|
+
function buildNotice(local, remote) {
|
|
56
|
+
return (`continuous-improvement ${remote} is available (you have ${local}). ` +
|
|
57
|
+
"Update: marketplace → /plugin marketplace update continuous-improvement, " +
|
|
58
|
+
"or npm → npx continuous-improvement install. Silence: CLAUDE_CI_UPDATE_CHECK=off");
|
|
59
|
+
}
|
|
60
|
+
/** Re-surface a still-valid pending upgrade straight from the cache, so a throttled
|
|
61
|
+
* invocation does not lose the nudge. Returns null unless the cache says an upgrade
|
|
62
|
+
* is pending AND that cached remote is still newer than the current local. */
|
|
63
|
+
export function pendingNotice(cache, local) {
|
|
64
|
+
if (!cache || cache.status !== "upgrade-available")
|
|
65
|
+
return null;
|
|
66
|
+
if (typeof cache.remote !== "string" || !isNewer(cache.remote, local))
|
|
67
|
+
return null;
|
|
68
|
+
return buildNotice(local, cache.remote);
|
|
69
|
+
}
|
|
70
|
+
/** The pure decision from a fresh fetch. `remote === null` (fetch failed) yields
|
|
71
|
+
* `unknown` with no notice and no cache write, so the next invocation retries
|
|
72
|
+
* rather than caching a false "up-to-date". */
|
|
73
|
+
export function evaluateUpdateCheck(args) {
|
|
74
|
+
const { local, remote, now } = args;
|
|
75
|
+
if (remote === null || parseSemver(remote) === null) {
|
|
76
|
+
return { status: "unknown", notice: null, nextCache: null };
|
|
77
|
+
}
|
|
78
|
+
if (isNewer(remote, local)) {
|
|
79
|
+
return {
|
|
80
|
+
status: "upgrade-available",
|
|
81
|
+
notice: buildNotice(local, remote),
|
|
82
|
+
nextCache: { status: "upgrade-available", local, remote, checkedAt: now },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
status: "up-to-date",
|
|
87
|
+
notice: null,
|
|
88
|
+
nextCache: { status: "up-to-date", local, remote, checkedAt: now },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** Fetch the `latest` dist-tag version for a package from the public npm registry.
|
|
92
|
+
* The only network in this module. Fails closed (returns null) on any error:
|
|
93
|
+
* timeout, non-2xx, malformed JSON, or a non-semver version string. fetchImpl and
|
|
94
|
+
* timeoutMs are injectable so callers and tests stay offline-deterministic. */
|
|
95
|
+
export async function fetchLatestNpmVersion(pkg, opts = {}) {
|
|
96
|
+
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
97
|
+
if (typeof fetchImpl !== "function")
|
|
98
|
+
return null;
|
|
99
|
+
try {
|
|
100
|
+
const res = await fetchImpl(`${NPM_REGISTRY}/${pkg}/latest`, {
|
|
101
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" },
|
|
102
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 5000),
|
|
103
|
+
});
|
|
104
|
+
if (!res.ok)
|
|
105
|
+
return null;
|
|
106
|
+
const body = (await res.json());
|
|
107
|
+
const version = typeof body.version === "string" ? body.version : null;
|
|
108
|
+
if (!version || parseSemver(version) === null)
|
|
109
|
+
return null;
|
|
110
|
+
return version;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
package/llms.txt
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# continuous-improvement
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 26 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.
|
|
4
4
|
|
|
5
5
|
## What This Is
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
The persistent-memory and discipline layer for AI coding agents. It carries the corrections Claude has already received from one session into the next, grounds each edit in real facts before it lands, and learns from every session so its competence compounds over time — research, plan, execute one thing at a time, verify, reflect, iterate, learn — building behavioral instincts via the Mulahazah learning system, so the same correction never has to be given twice and each run starts smarter than the last. Orchestration tools run a task; this is the layer that makes the lessons survive the run.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "continuous-improvement",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.16.0",
|
|
4
|
+
"description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 26 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts. Beginner: one /plugin install command. Expert: adds MCP tools and session hooks.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
7
7
|
"claude-code-skill",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"bin": {
|
|
30
30
|
"continuous-improvement": "bin/install.mjs",
|
|
31
31
|
"ci-lint-transcript": "bin/lint-transcript.mjs",
|
|
32
|
-
"ci": "bin/unified-cli.mjs"
|
|
32
|
+
"ci": "bin/unified-cli.mjs",
|
|
33
|
+
"ci-plan-pack": "bin/plan-pack.mjs"
|
|
33
34
|
},
|
|
34
35
|
"scripts": {
|
|
35
36
|
"build": "tsc -p tsconfig.json && node bin/generate-plugin-manifests.mjs && node -e \"const fs=require('node:fs'); for (const f of fs.readdirSync('bin')) { if (f.endsWith('.mjs')) fs.chmodSync('bin/'+f, 0o755); } for (const f of fs.readdirSync('hooks')) { if (f.endsWith('.mjs')) fs.chmodSync('hooks/'+f, 0o755); } for (const f of fs.readdirSync('lib')) { if (f.endsWith('.mjs')) fs.chmodSync('lib/'+f, 0o755); } for (const f of fs.readdirSync('plugins/continuous-improvement/bin')) { if (f.endsWith('.mjs')) fs.chmodSync('plugins/continuous-improvement/bin/'+f, 0o755); } for (const f of fs.readdirSync('plugins/continuous-improvement/lib')) { if (f.endsWith('.mjs')) fs.chmodSync('plugins/continuous-improvement/lib/'+f, 0o755); } for (const f of fs.readdirSync('plugins/continuous-improvement/hooks')) { if (f.endsWith('.mjs')) fs.chmodSync('plugins/continuous-improvement/hooks/'+f, 0o755); } for (const f of fs.readdirSync('scripts')) { if (f.endsWith('.mjs')) fs.chmodSync('scripts/'+f, 0o755); } for (const f of fs.readdirSync('synthetic-checks')) { if (f.endsWith('.mjs')) fs.chmodSync('synthetic-checks/'+f, 0o755); } \"",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"verify:skill-tiers": "node bin/check-skill-tiers.mjs",
|
|
44
45
|
"verify:skill-law-tag": "node bin/check-skill-law-tag.mjs",
|
|
45
46
|
"verify:skill-count": "node bin/check-skill-count.mjs",
|
|
47
|
+
"verify:skill-count-prose": "node bin/check-skill-count-prose.mjs",
|
|
46
48
|
"verify:docs-substrings": "node bin/check-docs-substrings.mjs",
|
|
47
49
|
"verify:everything-mirror": "node bin/check-everything-mirror.mjs",
|
|
48
50
|
"verify:routing-targets": "node bin/check-routing-targets.mjs",
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
"verify:scripts-citation-drift": "node bin/check-scripts-citation-drift.mjs",
|
|
52
54
|
"verify:third-party-shape": "node bin/check-third-party-shape.mjs",
|
|
53
55
|
"verify:tool-count": "node bin/check-tool-count.mjs",
|
|
54
|
-
"verify:all": "npm run verify:skill-mirror && npm run verify:skill-tiers && npm run verify:skill-law-tag && npm run verify:skill-count && npm run verify:docs-substrings && npm run verify:everything-mirror && npm run verify:routing-targets && npm run verify:doc-runtime-claims && npm run verify:test-imports-only && npm run verify:scripts-citation-drift && npm run verify:third-party-shape && npm run verify:tool-count && npm run typecheck"
|
|
56
|
+
"verify:all": "npm run verify:skill-mirror && npm run verify:skill-tiers && npm run verify:skill-law-tag && npm run verify:skill-count && npm run verify:skill-count-prose && npm run verify:docs-substrings && npm run verify:everything-mirror && npm run verify:routing-targets && npm run verify:doc-runtime-claims && npm run verify:test-imports-only && npm run verify:scripts-citation-drift && npm run verify:third-party-shape && npm run verify:tool-count && npm run typecheck"
|
|
55
57
|
},
|
|
56
58
|
"files": [
|
|
57
59
|
".claude-plugin/",
|
package/plugins/beginner.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "continuous-improvement",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.16.0",
|
|
4
4
|
"mode": "beginner",
|
|
5
|
-
"description": "Beginner mode: see what your agent learned, list its instincts, and request a session reflection. Bundles
|
|
5
|
+
"description": "Beginner mode: see what your agent learned, list its instincts, and request a session reflection. Bundles three grounding skills (gateguard, tdd-workflow, verification-loop) so research, memory, tests, and verification happen by default — every edit starts from facts, not guesses.",
|
|
6
6
|
"tools": [
|
|
7
7
|
{
|
|
8
8
|
"name": "ci_status",
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"plugins": [
|
|
8
8
|
{
|
|
9
9
|
"name": "continuous-improvement",
|
|
10
|
-
"description": "
|
|
11
|
-
"version": "3.
|
|
10
|
+
"description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 26 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.",
|
|
11
|
+
"version": "3.16.0",
|
|
12
12
|
"source": "./",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "naimkatiman"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "continuous-improvement",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.16.0",
|
|
4
|
+
"description": "The persistent-memory and runtime-discipline layer for Claude Code. It remembers the corrections you already gave, grounds every edit in real facts before it lands, and — through the Mulahazah engine — turns each fix into a reusable instinct, so a lesson learned once is applied automatically next time with no re-teaching. Built on the 7 Laws of AI Agent Discipline (research, plan, verify, reflect, learn) and shipped as 26 bundled skills, instinct-aware hooks, an MCP toolset for recall and reflection, and a GitHub Action transcript linter that feeds real work history back into sharper instincts.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "naimkatiman",
|
|
7
7
|
"url": "https://github.com/naimkatiman"
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Naim Katiman
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Naim Katiman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -35,7 +35,7 @@ the trade-off is fallback quality vs dedicated-skill quality.
|
|
|
35
35
|
- `tdd-workflow` — RED/GREEN/REFACTOR + 80% coverage gate
|
|
36
36
|
- `workspace-surface-audit` — environment + capability audit
|
|
37
37
|
- Tier-1/Tier-2 enforcement skills (`gateguard`, `verification-loop`,
|
|
38
|
-
`
|
|
38
|
+
`safety-guard`, `token-budget-advisor`,
|
|
39
39
|
`strategic-compact`, `wild-risa-balance`)
|
|
40
40
|
|
|
41
41
|
**Optional companions the orchestrator routes to (install separately if
|
|
@@ -27,25 +27,25 @@ const args = process.argv.slice(2);
|
|
|
27
27
|
const DRY_RUN = args.includes("--dry-run");
|
|
28
28
|
const HELP = args.includes("--help") || args.includes("-h");
|
|
29
29
|
if (HELP) {
|
|
30
|
-
console.log(`
|
|
31
|
-
backfill — Tag legacy thin-schema observation rows.
|
|
32
|
-
|
|
33
|
-
Usage:
|
|
34
|
-
npx continuous-improvement backfill Tag rows in place
|
|
35
|
-
npx continuous-improvement backfill --dry-run Report counts, write nothing
|
|
36
|
-
|
|
37
|
-
What it does:
|
|
38
|
-
Walks ~/.claude/instincts/<hash>/observations.jsonl files and adds a
|
|
39
|
-
schema field ("thin" or "rich") to every row that lacks one. Thin rows
|
|
40
|
-
came from the legacy bash fallback (jq missing) and have no input_summary;
|
|
41
|
-
rich rows came from the Node observer and have full tool_input.command /
|
|
42
|
-
Edit.file_path / etc.
|
|
43
|
-
|
|
44
|
-
Output:
|
|
45
|
-
- observations.jsonl rewritten in place (original kept as .bak)
|
|
46
|
-
- backfill-summary.json with per-project counts
|
|
47
|
-
|
|
48
|
-
Idempotent. Always exits 0 — failures go to stderr only.
|
|
30
|
+
console.log(`
|
|
31
|
+
backfill — Tag legacy thin-schema observation rows.
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
npx continuous-improvement backfill Tag rows in place
|
|
35
|
+
npx continuous-improvement backfill --dry-run Report counts, write nothing
|
|
36
|
+
|
|
37
|
+
What it does:
|
|
38
|
+
Walks ~/.claude/instincts/<hash>/observations.jsonl files and adds a
|
|
39
|
+
schema field ("thin" or "rich") to every row that lacks one. Thin rows
|
|
40
|
+
came from the legacy bash fallback (jq missing) and have no input_summary;
|
|
41
|
+
rich rows came from the Node observer and have full tool_input.command /
|
|
42
|
+
Edit.file_path / etc.
|
|
43
|
+
|
|
44
|
+
Output:
|
|
45
|
+
- observations.jsonl rewritten in place (original kept as .bak)
|
|
46
|
+
- backfill-summary.json with per-project counts
|
|
47
|
+
|
|
48
|
+
Idempotent. Always exits 0 — failures go to stderr only.
|
|
49
49
|
`);
|
|
50
50
|
process.exit(0);
|
|
51
51
|
}
|
|
File without changes
|
|
@@ -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
|
}
|
|
@@ -219,6 +219,7 @@ function readDistillObservations(projectHash) {
|
|
|
219
219
|
tool: getString(observation.tool),
|
|
220
220
|
input_summary: getString(observation.input_summary),
|
|
221
221
|
output_summary: getString(observation.output_summary),
|
|
222
|
+
event: getString(observation.event),
|
|
222
223
|
}));
|
|
223
224
|
}
|
|
224
225
|
function detectLevel(projectHash) {
|
|
@@ -508,10 +509,11 @@ function handleTool(name, params) {
|
|
|
508
509
|
}
|
|
509
510
|
case "ci_gateguard_clear": {
|
|
510
511
|
// Beginner-available on purpose: the GateGuard hook fires for every
|
|
511
|
-
// install, so the clearance action must too.
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
512
|
+
// install, so the clearance action must too. The hook's block reason now
|
|
513
|
+
// prints state_path (the session-scoped state file); honoring it writes
|
|
514
|
+
// the marker exactly where the hook looks. Without it the MCP server can't
|
|
515
|
+
// know the caller's session, so it falls back to the canonical session dir
|
|
516
|
+
// — correct only on the legacy unscoped path.
|
|
515
517
|
const rawList = Array.isArray(params.file_paths) ? params.file_paths : [];
|
|
516
518
|
const listPaths = rawList.filter((value) => typeof value === "string" && value.length > 0);
|
|
517
519
|
const single = getString(params.file_path).trim();
|
|
@@ -519,7 +521,24 @@ function handleTool(name, params) {
|
|
|
519
521
|
if (paths.length === 0) {
|
|
520
522
|
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
523
|
}
|
|
522
|
-
|
|
524
|
+
// state_path is a model-controlled argument, so it could be a traversal
|
|
525
|
+
// string (`../../etc/...`). dirname leaves `..` segments for the OS to
|
|
526
|
+
// resolve at write time, so without a bound this is an arbitrary-write
|
|
527
|
+
// primitive. Resolve + canonicalize, then require containment within the
|
|
528
|
+
// instincts root (the only tree the hook ever prints a state_path inside).
|
|
529
|
+
const rawStatePath = getString(params.state_path).trim();
|
|
530
|
+
let sessionDir;
|
|
531
|
+
if (rawStatePath) {
|
|
532
|
+
const resolvedKey = canonicalizeFileKey(resolve(rawStatePath));
|
|
533
|
+
const rootKey = canonicalizeFileKey(resolveInstinctsRoot());
|
|
534
|
+
if (resolvedKey !== rootKey && !resolvedKey.startsWith(`${rootKey}/`)) {
|
|
535
|
+
return error("state_path must resolve inside ~/.claude/instincts/. Pass it verbatim from the GateGuard block reason.");
|
|
536
|
+
}
|
|
537
|
+
sessionDir = dirname(resolve(rawStatePath));
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
sessionDir = resolveSessionDir();
|
|
541
|
+
}
|
|
523
542
|
const { cleared, skippedForCap } = clearFiles(sessionDir, paths);
|
|
524
543
|
const lines = [
|
|
525
544
|
"## GateGuard clearance",
|
|
@@ -837,6 +856,45 @@ function handleTool(name, params) {
|
|
|
837
856
|
"```",
|
|
838
857
|
].join("\n"));
|
|
839
858
|
}
|
|
859
|
+
case "ci_distill_from_workflow": {
|
|
860
|
+
if (MODE !== "expert") {
|
|
861
|
+
return error("ci_distill_from_workflow requires expert mode");
|
|
862
|
+
}
|
|
863
|
+
const run = workflowRunFromObservations(readDistillObservations(project.hash));
|
|
864
|
+
if (!run) {
|
|
865
|
+
return text("No completed-and-verified Workflow run found in this project's observations. " +
|
|
866
|
+
"A run qualifies when a `Workflow` tool call is followed by a passing verify/test/build in the same feed. " +
|
|
867
|
+
"Run a workflow, verify its output, then try `ci_distill_from_workflow` again.");
|
|
868
|
+
}
|
|
869
|
+
const draftInstinct = draftFromWorkflowRun(run);
|
|
870
|
+
if (!isSafeDraftId(draftInstinct.id)) {
|
|
871
|
+
return error(`Internal error: generated draft id "${draftInstinct.id}" failed the safety check.`);
|
|
872
|
+
}
|
|
873
|
+
const draft = serializeDraft(draftInstinct);
|
|
874
|
+
const draftsDir = join(INSTINCTS_DIR, project.hash, "drafts");
|
|
875
|
+
const draftPath = join(draftsDir, `${draftInstinct.id}.yaml`);
|
|
876
|
+
try {
|
|
877
|
+
mkdirSync(draftsDir, { recursive: true });
|
|
878
|
+
writeFileSync(draftPath, draft);
|
|
879
|
+
}
|
|
880
|
+
catch (err) {
|
|
881
|
+
return error(`Failed to write draft to ${draftPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
882
|
+
}
|
|
883
|
+
return text([
|
|
884
|
+
"## Draft written from a verified workflow run",
|
|
885
|
+
"",
|
|
886
|
+
`**Workflow:** ${run.name}`,
|
|
887
|
+
`**Path:** ${draftPath}`,
|
|
888
|
+
"",
|
|
889
|
+
"Edit the body to capture the real recipe (preconditions, concrete steps, gotchas), then promote with:",
|
|
890
|
+
"",
|
|
891
|
+
` ci_distill_promote id=${draftInstinct.id}`,
|
|
892
|
+
"",
|
|
893
|
+
"```yaml",
|
|
894
|
+
draft.trimEnd(),
|
|
895
|
+
"```",
|
|
896
|
+
].join("\n"));
|
|
897
|
+
}
|
|
840
898
|
case "ci_distill_promote": {
|
|
841
899
|
if (MODE !== "expert") {
|
|
842
900
|
return error("ci_distill_promote requires expert mode");
|
|
File without changes
|