continuous-improvement 3.20.0 → 3.21.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 +10 -0
- package/QUICKSTART.md +1 -1
- package/README.md +7 -6
- package/bin/check-landing-version.mjs +63 -0
- package/bin/check-scripts-citation-drift.mjs +61 -13
- package/bin/generate-plugin-manifests.mjs +2 -0
- package/bin/install.mjs +29 -60
- package/commands/production-readiness-review.md +5 -4
- package/commands/simplicity-review.md +35 -0
- package/commands/verify-install.md +2 -2
- package/hooks/gateguard.mjs +22 -3
- package/hooks/query-cost-nudge.mjs +1 -0
- package/hooks/session.mjs +85 -0
- package/hooks/typecheck-stop.mjs +2 -1
- package/lib/plugin-metadata.mjs +18 -20
- package/llms.txt +1 -1
- package/package.json +6 -4
- package/plugins/beginner.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +2 -2
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +2 -2
- package/plugins/continuous-improvement/README.md +1 -0
- package/plugins/continuous-improvement/commands/production-readiness-review.md +5 -4
- package/plugins/continuous-improvement/commands/simplicity-review.md +35 -0
- package/plugins/continuous-improvement/commands/verify-install.md +2 -2
- package/plugins/continuous-improvement/hooks/gateguard.mjs +22 -3
- package/plugins/continuous-improvement/hooks/hooks.json +15 -16
- package/plugins/continuous-improvement/hooks/query-cost-nudge.mjs +1 -0
- package/plugins/continuous-improvement/hooks/session.mjs +85 -0
- package/plugins/continuous-improvement/hooks/typecheck-stop.mjs +2 -1
- package/plugins/continuous-improvement/lib/plugin-metadata.mjs +18 -20
- package/plugins/continuous-improvement/scripts/README.md +33 -0
- package/plugins/continuous-improvement/scripts/detect-deploy-target.sh +66 -0
- package/plugins/continuous-improvement/scripts/get-deployed-sha.sh +113 -0
- package/plugins/continuous-improvement/scripts/git-state-snapshot.sh +48 -0
- package/plugins/continuous-improvement/scripts/resolve-verify-ladder.mjs +241 -0
- package/plugins/continuous-improvement/scripts/route-recommendation.mjs +178 -0
- package/plugins/continuous-improvement/scripts/route-recommendation.routes.json +213 -0
- package/plugins/continuous-improvement/scripts/run-synthetic.mjs +298 -0
- package/plugins/continuous-improvement/scripts/scan-past-mistakes.mjs +285 -0
- package/plugins/continuous-improvement/skills/README.md +1 -0
- package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +2 -2
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +2 -2
- package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +3 -2
- package/plugins/continuous-improvement/skills/reconcile/SKILL.md +1 -1
- package/plugins/continuous-improvement/skills/simplicity-review/SKILL.md +80 -0
- package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +5 -5
- package/plugins/continuous-improvement/skills/workspace-surface-audit/SKILL.md +1 -1
- package/plugins/continuous-improvement/skills/worktree-safety/SKILL.md +1 -1
- package/plugins/expert.json +1 -1
- package/scripts/README.md +33 -0
- package/scripts/detect-deploy-target.sh +66 -0
- package/scripts/get-deployed-sha.sh +113 -0
- package/scripts/git-state-snapshot.sh +48 -0
- package/scripts/resolve-verify-ladder.mjs +241 -0
- package/scripts/route-recommendation.mjs +178 -0
- package/scripts/route-recommendation.routes.json +213 -0
- package/scripts/run-synthetic.mjs +298 -0
- package/scripts/scan-past-mistakes.mjs +285 -0
- package/skills/deploy-receipt.md +2 -2
- package/skills/gateguard.md +2 -2
- package/skills/proceed-with-the-recommendation.md +3 -2
- package/skills/reconcile.md +1 -1
- package/skills/simplicity-review.md +80 -0
- package/skills/verification-loop.md +5 -5
- package/skills/workspace-surface-audit.md +1 -1
- package/skills/worktree-safety.md +1 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { resolveHomeDir } from "../lib/resolve-home-dir.mjs";
|
|
7
|
+
function read(path) {
|
|
8
|
+
try {
|
|
9
|
+
return readFileSync(path, "utf8");
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function eventFromStdin() {
|
|
16
|
+
const raw = read(0);
|
|
17
|
+
if (!raw)
|
|
18
|
+
return null;
|
|
19
|
+
try {
|
|
20
|
+
const payload = JSON.parse(raw);
|
|
21
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
22
|
+
return null;
|
|
23
|
+
const event = payload.hook_event_name ?? payload.hook_type ?? payload.event_type;
|
|
24
|
+
return event === "SessionStart" || event === "SessionEnd" ? event : "unknown";
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function projectRoot() {
|
|
31
|
+
if (process.env.CLAUDE_PROJECT_DIR)
|
|
32
|
+
return process.env.CLAUDE_PROJECT_DIR;
|
|
33
|
+
try {
|
|
34
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
37
|
+
}).trim() || "global";
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return "global";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function yamlFiles(dir) {
|
|
44
|
+
try {
|
|
45
|
+
return readdirSync(dir)
|
|
46
|
+
.filter((name) => name.endsWith(".yaml"))
|
|
47
|
+
.map((name) => join(dir, name));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function main() {
|
|
54
|
+
const event = eventFromStdin();
|
|
55
|
+
if (event === null)
|
|
56
|
+
return;
|
|
57
|
+
if (event === "SessionEnd") {
|
|
58
|
+
process.stderr.write("[continuous-improvement] Session ending. Run /continuous-improvement to reflect and capture learnings.\n");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const home = resolveHomeDir();
|
|
62
|
+
if (!home)
|
|
63
|
+
return;
|
|
64
|
+
const instinctsRoot = join(home, ".claude", "instincts");
|
|
65
|
+
const hash = createHash("sha256").update(projectRoot()).digest("hex").slice(0, 12);
|
|
66
|
+
const projectDir = join(instinctsRoot, hash);
|
|
67
|
+
const files = [...yamlFiles(projectDir), ...yamlFiles(join(instinctsRoot, "global"))];
|
|
68
|
+
const observations = read(join(projectDir, "observations.jsonl")).split(/\r?\n/).filter(Boolean).length;
|
|
69
|
+
let level = observations >= 20 || files.length > 0 ? "ANALYZE" : "CAPTURE";
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
const value = Number(read(file).match(/^confidence:\s*([0-9]*\.?[0-9]+)/m)?.[1]);
|
|
72
|
+
if (Number.isFinite(value) && value >= 0.7) {
|
|
73
|
+
level = "AUTO-APPLY";
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
if (Number.isFinite(value) && value >= 0.5)
|
|
77
|
+
level = "SUGGEST";
|
|
78
|
+
}
|
|
79
|
+
process.stderr.write(`[continuous-improvement] Level: ${level} | Observations: ${observations} | Instincts: ${files.length}\n`);
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
main();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
}
|
|
@@ -51,7 +51,8 @@ function collectChangedFiles(root) {
|
|
|
51
51
|
};
|
|
52
52
|
const unstaged = run(["diff", "--name-only", "--diff-filter=ACMR"]);
|
|
53
53
|
const staged = run(["diff", "--cached", "--name-only", "--diff-filter=ACMR"]);
|
|
54
|
-
|
|
54
|
+
const untracked = run(["ls-files", "--others", "--exclude-standard"]);
|
|
55
|
+
return [...parseChangedFiles(unstaged), ...parseChangedFiles(staged), ...parseChangedFiles(untracked)];
|
|
55
56
|
}
|
|
56
57
|
function hasNpmTypecheckScript(root) {
|
|
57
58
|
try {
|
|
@@ -26,7 +26,7 @@ const KEYWORDS = [
|
|
|
26
26
|
"transcript-linter",
|
|
27
27
|
];
|
|
28
28
|
const CLAUDE_PLUGIN_CATEGORY = "productivity";
|
|
29
|
-
const SHARED_PLUGIN_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
|
|
29
|
+
const SHARED_PLUGIN_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 28 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.";
|
|
30
30
|
// Four vendored upstream companions registered alongside the CI plugin.
|
|
31
31
|
// Each entry points at a pinned-SHA snapshot under third-party/<name>/.
|
|
32
32
|
// See third-party/MANIFEST.md for refresh recipes and per-snapshot
|
|
@@ -457,76 +457,74 @@ export function getClaudePluginManifest() {
|
|
|
457
457
|
};
|
|
458
458
|
}
|
|
459
459
|
export function getPluginHooksConfig() {
|
|
460
|
+
// Cold Node startup on loaded Windows hosts has exceeded five seconds.
|
|
461
|
+
const hookTimeoutSeconds = 30;
|
|
460
462
|
const gateguardCommand = {
|
|
461
463
|
type: "command",
|
|
462
464
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/gateguard.mjs\"",
|
|
463
|
-
timeout:
|
|
465
|
+
timeout: hookTimeoutSeconds,
|
|
464
466
|
};
|
|
465
467
|
const companionPreferenceCommand = {
|
|
466
468
|
type: "command",
|
|
467
469
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/companion-preference.mjs\"",
|
|
468
|
-
timeout:
|
|
470
|
+
timeout: hookTimeoutSeconds,
|
|
469
471
|
};
|
|
470
472
|
const hookPackCommand = {
|
|
471
473
|
type: "command",
|
|
472
474
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/hook-pack.mjs\"",
|
|
473
|
-
timeout:
|
|
475
|
+
timeout: hookTimeoutSeconds,
|
|
474
476
|
};
|
|
475
477
|
const observeCommand = {
|
|
476
478
|
type: "command",
|
|
477
|
-
command: "
|
|
478
|
-
timeout:
|
|
479
|
+
command: "node \"${CLAUDE_PLUGIN_ROOT}/bin/observe.mjs\"",
|
|
480
|
+
timeout: hookTimeoutSeconds,
|
|
479
481
|
};
|
|
480
482
|
const sessionCommand = {
|
|
481
483
|
type: "command",
|
|
482
|
-
command: "
|
|
483
|
-
timeout:
|
|
484
|
+
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session.mjs\"",
|
|
485
|
+
timeout: hookTimeoutSeconds,
|
|
484
486
|
};
|
|
485
487
|
const threeSectionCloseCommand = {
|
|
486
488
|
type: "command",
|
|
487
489
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/three-section-close.mjs\"",
|
|
488
|
-
timeout:
|
|
490
|
+
timeout: hookTimeoutSeconds,
|
|
489
491
|
};
|
|
490
492
|
const goalDriftStopCommand = {
|
|
491
493
|
type: "command",
|
|
492
494
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/goal-drift-stop.mjs\"",
|
|
493
|
-
timeout:
|
|
495
|
+
timeout: hookTimeoutSeconds,
|
|
494
496
|
};
|
|
495
497
|
const workflowDistillCommand = {
|
|
496
498
|
type: "command",
|
|
497
499
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/workflow-distill.mjs\"",
|
|
498
|
-
timeout:
|
|
500
|
+
timeout: hookTimeoutSeconds,
|
|
499
501
|
};
|
|
500
502
|
const typecheckStopCommand = {
|
|
501
503
|
type: "command",
|
|
502
504
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/typecheck-stop.mjs\"",
|
|
503
|
-
|
|
504
|
-
// (off by default) and near-zero cost when off / no TS file changed; on an
|
|
505
|
-
// internal timeout it fails open (allow) rather than blocking.
|
|
506
|
-
timeout: 30,
|
|
505
|
+
timeout: hookTimeoutSeconds,
|
|
507
506
|
};
|
|
508
507
|
const queryCostNudgeCommand = {
|
|
509
508
|
type: "command",
|
|
510
509
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/query-cost-nudge.mjs\"",
|
|
511
|
-
timeout:
|
|
510
|
+
timeout: hookTimeoutSeconds,
|
|
512
511
|
};
|
|
513
512
|
const routePromptCommand = {
|
|
514
513
|
type: "command",
|
|
515
514
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/route-prompt.mjs\"",
|
|
516
|
-
timeout:
|
|
515
|
+
timeout: hookTimeoutSeconds,
|
|
517
516
|
};
|
|
518
517
|
const recallBriefingCommand = {
|
|
519
518
|
type: "command",
|
|
520
519
|
command: "node \"${CLAUDE_PLUGIN_ROOT}/hooks/recall-briefing.mjs\"",
|
|
521
|
-
timeout:
|
|
520
|
+
timeout: hookTimeoutSeconds,
|
|
522
521
|
};
|
|
523
522
|
return {
|
|
524
|
-
description: "Gateguard fact-forcing PreToolUse, companion-preference enforcement, observation, session lifecycle, 3-section-close discipline, goal-drift Stop gate, opt-in workflow-distill Stop nudge, opt-in typecheck Stop gate, opt-in query-cost Stop nudge, and UserPromptSubmit lazy-routing plus opt-in proactive recall-briefing hooks for continuous-improvement.",
|
|
525
523
|
hooks: {
|
|
526
524
|
// gateguard runs FIRST on PreToolUse so its block decision short-circuits
|
|
527
525
|
// before companion-preference sees the call. companion-preference runs
|
|
528
526
|
// second on Skill tool calls; it is a no-op under ci-first (the default)
|
|
529
|
-
// and never blocks under companions-first.
|
|
527
|
+
// and never blocks under companions-first. The observer only runs on
|
|
530
528
|
// PostToolUse: gateguard-blocked calls are intentionally not observed so
|
|
531
529
|
// PreToolUse stays at two subprocesses on the hot path. route-prompt
|
|
532
530
|
// fires on UserPromptSubmit and emits a system-reminder when a prompt
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# `scripts/` — deterministic primitives skills cite
|
|
2
|
+
|
|
3
|
+
This directory holds small, hand-authored scripts that skills (and hooks) cite instead of restating fixed operations inline. The motivation is the skills audit's "deterministic vs non-deterministic" axis: when a step inside a skill is the same operation every time (a fixed git command, a regex parse, a path lookup), it does not belong in the LLM-driven part of the skill — it belongs here, where the script runs the same way every invocation, costs no tokens, and stays in one place so multiple skills can share it.
|
|
4
|
+
|
|
5
|
+
## Conventions
|
|
6
|
+
|
|
7
|
+
- **Hand-authored, no `.mts` source.** Files in `scripts/` are not part of the `tsc` build pipeline (see [`CLAUDE.md`](../CLAUDE.md) → "Build pipeline"). The pipeline owns `bin/`, `lib/`, `test/`. `scripts/` is the bare-metal home for shell scripts and one-off Node utilities skills cite directly.
|
|
8
|
+
- **One concern per script.** Each script does one thing and prints either machine-readable output (JSON envelope, single value) or a stable fenced block. Skills consume the output; they do not re-derive it.
|
|
9
|
+
- **Cross-platform shape.** Bash scripts run via Git Bash on Windows, native bash on macOS/Linux. Tests for bash scripts skip when bash is not on PATH (see `src/test/hook.test.mts` for the established skip pattern).
|
|
10
|
+
- **Cite from skills.** When a skill needs a primitive, the skill body cites the script path (`scripts/<name>.<ext>`) and quotes the expected output shape once. The skill does not restate the script's inner mechanics.
|
|
11
|
+
|
|
12
|
+
## Inventory
|
|
13
|
+
|
|
14
|
+
| Script | Purpose | Cited by |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `git-state-snapshot.sh` | JSON envelope `{head, upstream, dirty, root, branch}` for the current git working tree | `skills/gateguard.md` (Parallel-Actor Gate), `skills/worktree-safety.md` (Root + branch), `skills/workspace-surface-audit.md` (Environment Grain — parallel-actor row), `skills/reconcile.md` (Detect a Concurrent Writer) |
|
|
17
|
+
| `detect-deploy-target.sh` | Detect the auto-deploy provider for the repo at the current working directory (or first arg). Prints one of `railway`/`cloudflare`/`vercel`/`netlify`/`fly`/`appengine`/`apprunner`/`gha-deploy`/`none`. Always exits 0. | `skills/verification-loop.md` (Phase 8 deploy-receipt handoff), `skills/deploy-receipt.md` (When to Activate gate) |
|
|
18
|
+
| `get-deployed-sha.sh` | Per-provider deployed-SHA extraction. Default mode runs the CLI; `--show-command <provider>` prints the pipeline shape without executing (useful for citation, dry-run, tests). | `skills/verification-loop.md` (Phase 8), `skills/deploy-receipt.md` (Route A — provider CLI extraction) |
|
|
19
|
+
| `resolve-verify-ladder.mjs` | Resolve the per-project verification ladder for Phase 0 of `verification-loop`. Encodes the four-step priority — manifest > package.json sniff > per-language toolchain > ask-operator. Default mode prints the fenced block the skill displays; `--json` mode emits a JSON object for machine consumption. | `skills/verification-loop.md` (Phase 0) |
|
|
20
|
+
| `scan-past-mistakes.mjs` | Scan the three Past-Mistake Acknowledgment Gate surfaces — `~/.claude/instincts/<hash>/observations.jsonl` (last N failure/correction rows), `~/.claude/projects/<hash>/memory/feedback_*.md`, and `<root>/CLAUDE.md` "## Past Mistakes" table. Active-in-scope judgment is the LLM's job; the script provides quotes + citations only. | `skills/proceed-with-the-recommendation.md` (Phase 0 Rule 1) |
|
|
21
|
+
| `route-recommendation.mjs` + `route-recommendation.routes.json` | Match a recommendation item to its preferred-skill chain + inline fallback from the Phase 3 routing table (29 rows). Data file is the programmatic source of truth; the skill's table is documentation that mirrors it. Default mode prints a match block; `--json` for machine consumption; `--list` enumerates all rows. | `skills/proceed-with-the-recommendation.md` (Phase 3 routing table) |
|
|
22
|
+
| `run-synthetic.mjs` | Phase 9 runner: invoke every `*.synthetic.{sh,mjs,ts,py}` in `synthetic-checks/`, inject `BASE_URL`/`BASELINE_URL`/`EXPECTED_SHA`/`DEPLOY_BRANCH`/`RECEIPT_TIMESTAMP` (unset vars pass through as `""`), capture stdout/stderr/exit per check, aggregate. Default mode prints the report block; `--json` for machine consumption; `--fail-fast` halts on first drift or timeout; `--timeout <sec>` per-check wall-clock cap; `--show-command` dry-run. Exit 0 all-pass, 1 drift, 2 config error, 3 usage. | `skills/verification-loop.md` (Phase 9 — production-vs-baseline diff) |
|
|
23
|
+
|
|
24
|
+
When a new script lands here, add a row to this table in the same PR and cite the script from at least one skill — otherwise the script is dead code on arrival.
|
|
25
|
+
|
|
26
|
+
## Relationship to other locations
|
|
27
|
+
|
|
28
|
+
- `bin/` — generated CLI entrypoints (`.mjs` from `src/bin/*.mts`). Do not hand-edit; see [`CLAUDE.md`](../CLAUDE.md) → "Build pipeline".
|
|
29
|
+
- `lib/` — generated library code (`.mjs` from `src/lib/*.mts`). Same rule.
|
|
30
|
+
- `hooks/` — generated PreToolUse / PostToolUse / Stop hooks (`.mjs` from `src/hooks/*.mts`). Wired in `plugins/continuous-improvement/hooks/hooks.json`.
|
|
31
|
+
- `scripts/` — this directory. Hand-authored primitives skills cite.
|
|
32
|
+
|
|
33
|
+
When deciding where a new piece of code belongs: if it implements a runtime hook the harness will call, it goes in `src/hooks/`. If it's a verification or CLI entrypoint the plugin or `npm run` invokes, it goes in `src/bin/`. If it's reusable logic shared between those, it goes in `src/lib/`. If it's a small primitive a skill body cites by path (and the operator or harness runs ad hoc), it goes here.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/detect-deploy-target.sh
|
|
3
|
+
#
|
|
4
|
+
# Detect the auto-deploy provider for the repo rooted at the current working
|
|
5
|
+
# directory (or the first argument, if supplied). Composability primitive used
|
|
6
|
+
# by skills and hooks that need to know "does this repo auto-deploy, and if so
|
|
7
|
+
# from where?" without each restating the file-marker table.
|
|
8
|
+
#
|
|
9
|
+
# Output: one of
|
|
10
|
+
# railway | cloudflare | vercel | netlify | fly | appengine | apprunner |
|
|
11
|
+
# gha-deploy | none
|
|
12
|
+
#
|
|
13
|
+
# Resolution priority (first match wins):
|
|
14
|
+
# 1. railway.toml | railway.json → railway
|
|
15
|
+
# 2. wrangler.toml | wrangler.jsonc → cloudflare
|
|
16
|
+
# 3. vercel.json | .vercel/ → vercel
|
|
17
|
+
# 4. netlify.toml → netlify
|
|
18
|
+
# 5. fly.toml → fly
|
|
19
|
+
# 6. app.yaml → appengine
|
|
20
|
+
# 7. apprunner.yaml → apprunner
|
|
21
|
+
# 8. .github/workflows/*.{yml,yaml} containing "deploy:" job → gha-deploy
|
|
22
|
+
# 9. nothing matched → none
|
|
23
|
+
#
|
|
24
|
+
# Always exits 0. `none` is a valid result, not an error condition.
|
|
25
|
+
#
|
|
26
|
+
# Cited by:
|
|
27
|
+
# - skills/verification-loop.md Phase 8 (deploy-receipt handoff trigger)
|
|
28
|
+
# - skills/deploy-receipt.md "When to Activate" gate
|
|
29
|
+
|
|
30
|
+
set -u
|
|
31
|
+
|
|
32
|
+
ROOT="${1:-$PWD}"
|
|
33
|
+
|
|
34
|
+
emit() {
|
|
35
|
+
printf '%s\n' "$1"
|
|
36
|
+
exit 0
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Order is the contract — earlier rows shadow later ones when multiple
|
|
40
|
+
# markers exist in the same repo.
|
|
41
|
+
[ -f "$ROOT/railway.toml" ] && emit railway
|
|
42
|
+
[ -f "$ROOT/railway.json" ] && emit railway
|
|
43
|
+
[ -f "$ROOT/wrangler.toml" ] && emit cloudflare
|
|
44
|
+
[ -f "$ROOT/wrangler.jsonc" ] && emit cloudflare
|
|
45
|
+
[ -f "$ROOT/vercel.json" ] && emit vercel
|
|
46
|
+
[ -d "$ROOT/.vercel" ] && emit vercel
|
|
47
|
+
[ -f "$ROOT/netlify.toml" ] && emit netlify
|
|
48
|
+
[ -f "$ROOT/fly.toml" ] && emit fly
|
|
49
|
+
[ -f "$ROOT/app.yaml" ] && emit appengine
|
|
50
|
+
[ -f "$ROOT/apprunner.yaml" ] && emit apprunner
|
|
51
|
+
|
|
52
|
+
# GitHub Actions deploy workflow: scan .github/workflows/*.{yml,yaml} for a
|
|
53
|
+
# job whose key is literally `deploy:`. Cheap and conservative — false
|
|
54
|
+
# positives (a non-deploy job named "deploy") are unlikely; false negatives
|
|
55
|
+
# (a deploy job named something else) require the repo to declare the
|
|
56
|
+
# provider via one of the file markers above instead.
|
|
57
|
+
if [ -d "$ROOT/.github/workflows" ]; then
|
|
58
|
+
if grep -lE '^[[:space:]]*deploy:[[:space:]]*$' \
|
|
59
|
+
"$ROOT/.github/workflows/"*.yml \
|
|
60
|
+
"$ROOT/.github/workflows/"*.yaml 2>/dev/null \
|
|
61
|
+
| head -n 1 | grep -q .; then
|
|
62
|
+
emit gha-deploy
|
|
63
|
+
fi
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
emit none
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/get-deployed-sha.sh
|
|
3
|
+
#
|
|
4
|
+
# Print the currently-deployed commit SHA for an auto-deploy provider, or
|
|
5
|
+
# print the CLI command shape (with `--show-command`) without executing it.
|
|
6
|
+
# Composability primitive that owns the per-provider CLI knowledge so skills
|
|
7
|
+
# can cite one path instead of restating the 5-provider extraction table.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# bash scripts/get-deployed-sha.sh <provider>
|
|
11
|
+
# bash scripts/get-deployed-sha.sh --show-command <provider>
|
|
12
|
+
#
|
|
13
|
+
# Providers: railway | cloudflare | vercel | netlify | fly
|
|
14
|
+
#
|
|
15
|
+
# Default mode: runs the provider CLI, pipes through jq, prints the SHA on
|
|
16
|
+
# stdout. Requires the CLI to be installed and authenticated; exits 3 with a
|
|
17
|
+
# clear error if the CLI is missing.
|
|
18
|
+
#
|
|
19
|
+
# --show-command mode: prints the command pipeline that would run, without
|
|
20
|
+
# executing it. Useful for skill citations, dry-runs, and tests that should
|
|
21
|
+
# not require live CLI auth.
|
|
22
|
+
#
|
|
23
|
+
# Exit codes:
|
|
24
|
+
# 0 — SHA printed (default mode) or command printed (--show-command mode)
|
|
25
|
+
# 2 — missing or unknown provider (usage error)
|
|
26
|
+
# 3 — required CLI not installed (default mode only)
|
|
27
|
+
# non-zero — CLI failure (passed through)
|
|
28
|
+
#
|
|
29
|
+
# Cited by:
|
|
30
|
+
# - skills/verification-loop.md Phase 8 (deploy-receipt handoff trigger)
|
|
31
|
+
# - skills/deploy-receipt.md Route A (provider CLI extraction)
|
|
32
|
+
|
|
33
|
+
set -u
|
|
34
|
+
|
|
35
|
+
SHOW_COMMAND=false
|
|
36
|
+
PROVIDER=""
|
|
37
|
+
|
|
38
|
+
while [ $# -gt 0 ]; do
|
|
39
|
+
case "$1" in
|
|
40
|
+
--show-command)
|
|
41
|
+
SHOW_COMMAND=true
|
|
42
|
+
shift
|
|
43
|
+
;;
|
|
44
|
+
-h|--help)
|
|
45
|
+
sed -n '2,/^$/p' "$0" >&2
|
|
46
|
+
exit 0
|
|
47
|
+
;;
|
|
48
|
+
*)
|
|
49
|
+
if [ -z "$PROVIDER" ]; then
|
|
50
|
+
PROVIDER="$1"
|
|
51
|
+
else
|
|
52
|
+
printf 'usage: get-deployed-sha.sh [--show-command] <provider>\n' >&2
|
|
53
|
+
exit 2
|
|
54
|
+
fi
|
|
55
|
+
shift
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
if [ -z "$PROVIDER" ]; then
|
|
61
|
+
printf 'usage: get-deployed-sha.sh [--show-command] <provider>\n' >&2
|
|
62
|
+
exit 2
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Single source of truth for the per-provider command pipeline. Each value is
|
|
66
|
+
# the literal pipeline that would run; the jq filter extracts the SHA.
|
|
67
|
+
case "$PROVIDER" in
|
|
68
|
+
railway)
|
|
69
|
+
CMD='railway status --json | jq -r .deployments[0].meta.commitHash'
|
|
70
|
+
CLI=railway
|
|
71
|
+
;;
|
|
72
|
+
cloudflare)
|
|
73
|
+
CMD='wrangler deployments list --json | jq -r .[0].metadata.deployment_trigger.metadata.commit_hash'
|
|
74
|
+
CLI=wrangler
|
|
75
|
+
;;
|
|
76
|
+
vercel)
|
|
77
|
+
CMD='vercel inspect "$(vercel ls --json | jq -r .[0].url)" --json | jq -r .gitSource.sha'
|
|
78
|
+
CLI=vercel
|
|
79
|
+
;;
|
|
80
|
+
netlify)
|
|
81
|
+
CMD='netlify api listSiteDeploys --data="{\"site_id\":\"$NETLIFY_SITE_ID\"}" | jq -r .[0].commit_ref'
|
|
82
|
+
CLI=netlify
|
|
83
|
+
;;
|
|
84
|
+
fly)
|
|
85
|
+
CMD='fly releases --json | jq -r .[0].commit_sha'
|
|
86
|
+
CLI=fly
|
|
87
|
+
;;
|
|
88
|
+
*)
|
|
89
|
+
printf 'unknown or unsupported provider: %s\n' "$PROVIDER" >&2
|
|
90
|
+
printf 'supported: railway | cloudflare | vercel | netlify | fly\n' >&2
|
|
91
|
+
exit 2
|
|
92
|
+
;;
|
|
93
|
+
esac
|
|
94
|
+
|
|
95
|
+
if [ "$SHOW_COMMAND" = "true" ]; then
|
|
96
|
+
printf '%s\n' "$CMD"
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
if ! command -v "$CLI" >/dev/null 2>&1; then
|
|
101
|
+
printf 'required CLI "%s" not installed for provider "%s"\n' "$CLI" "$PROVIDER" >&2
|
|
102
|
+
exit 3
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
106
|
+
printf 'required CLI "jq" not installed\n' >&2
|
|
107
|
+
exit 3
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Execute the pipeline. eval is intentional — the per-provider CMD contains
|
|
111
|
+
# pipes and command substitution that need shell interpretation. CMDs are
|
|
112
|
+
# sourced from the literal table above, not user input.
|
|
113
|
+
eval "$CMD"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/git-state-snapshot.sh
|
|
3
|
+
#
|
|
4
|
+
# Emit a single-line JSON envelope describing the current git working-tree
|
|
5
|
+
# state. Composability primitive used by skills that need to baseline or check
|
|
6
|
+
# git state without each restating the same 3-command triple.
|
|
7
|
+
#
|
|
8
|
+
# Fields:
|
|
9
|
+
# - head: short SHA returned by `git rev-parse --short HEAD`
|
|
10
|
+
# - upstream: short SHA of `@{u}` if the branch tracks an upstream, else "none"
|
|
11
|
+
# - dirty: integer count of lines from `git status --porcelain` (0 == clean)
|
|
12
|
+
# - root: absolute path from `git rev-parse --show-toplevel`
|
|
13
|
+
# - branch: `git symbolic-ref --short HEAD`, else "detached"
|
|
14
|
+
#
|
|
15
|
+
# Outside a git repository the script prints `{"error":"not-a-git-repo"}` and
|
|
16
|
+
# exits 1. All other failures are treated as a non-git-repo condition rather
|
|
17
|
+
# than emitting a partial envelope.
|
|
18
|
+
#
|
|
19
|
+
# Cited by:
|
|
20
|
+
# - skills/gateguard.md (Parallel-Actor Gate baseline + divergence)
|
|
21
|
+
# - skills/worktree-safety.md (Root + branch alignment)
|
|
22
|
+
# - skills/workspace-surface-audit.md (Environment Grain — parallel-actor row)
|
|
23
|
+
|
|
24
|
+
set -u
|
|
25
|
+
|
|
26
|
+
head=$(git rev-parse --short HEAD 2>/dev/null) || {
|
|
27
|
+
printf '{"error":"not-a-git-repo"}\n'
|
|
28
|
+
exit 1
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if upstream=$(git rev-parse --short '@{u}' 2>/dev/null); then
|
|
32
|
+
upstream_field=$(printf '"%s"' "$upstream")
|
|
33
|
+
else
|
|
34
|
+
upstream_field='"none"'
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
38
|
+
|
|
39
|
+
root=$(git rev-parse --show-toplevel 2>/dev/null || printf 'unknown')
|
|
40
|
+
|
|
41
|
+
if branch=$(git symbolic-ref --short HEAD 2>/dev/null); then
|
|
42
|
+
branch_field=$(printf '"%s"' "$branch")
|
|
43
|
+
else
|
|
44
|
+
branch_field='"detached"'
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
printf '{"head":"%s","upstream":%s,"dirty":%s,"root":"%s","branch":%s}\n' \
|
|
48
|
+
"$head" "$upstream_field" "$dirty" "$root" "$branch_field"
|