instar 1.3.296 → 1.3.297
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/package.json
CHANGED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// safe-git-allow: CI-only gate script — single read-only `git diff --name-status`
|
|
3
|
+
// against the Actions checkout; runs on ubuntu runners where the TS
|
|
4
|
+
// SafeGitExecutor is not importable from a standalone .mjs.
|
|
5
|
+
/**
|
|
6
|
+
* decision-audit-presence-check — gate-bypass detector (task #81 close-out).
|
|
7
|
+
*
|
|
8
|
+
* Every commit that touches in-scope files (src/, scripts/, .husky/, skills/
|
|
9
|
+
* code) is supposed to pass through the local instar-dev pre-commit gate,
|
|
10
|
+
* which writes + stages a decision-audit record. When the gate is silently
|
|
11
|
+
* absent — the live case: a worktree created with raw `git worktree add` has
|
|
12
|
+
* no husky shim, so `git commit` runs ZERO hooks — the commits arrive with no
|
|
13
|
+
* audit record, and nothing notices. CI re-runs the substantive checks, but
|
|
14
|
+
* the audit trail has holes and gate-block UX (tier floors, deferral
|
|
15
|
+
* detection) never fired for those commits.
|
|
16
|
+
*
|
|
17
|
+
* Structure > Willpower: this check makes the bypass VISIBLE at the PR
|
|
18
|
+
* boundary. A PR whose changes include in-scope files must also carry gate
|
|
19
|
+
* evidence — a per-entry decision file (post-#827: one
|
|
20
|
+
* `.instar/instar-dev-decisions/<ts>-<slug>.json` added per gate run) or, as
|
|
21
|
+
* transition grace for PRs authored under the pre-#827 writer, a modification
|
|
22
|
+
* to the legacy `.instar/instar-dev-decisions.jsonl`.
|
|
23
|
+
*
|
|
24
|
+
* Exemptions mirror the eli16 PR gate: bot authors and the automated
|
|
25
|
+
* release-cut PR.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { execFileSync } from 'node:child_process';
|
|
29
|
+
|
|
30
|
+
// Keep in sync with inScope() in scripts/instar-dev-precommit.js — the gate
|
|
31
|
+
// this check detects bypasses OF. (A drift here only weakens detection, never
|
|
32
|
+
// blocks a legitimate PR: extra in-scope prefixes would require evidence from
|
|
33
|
+
// PRs the local gate also covers.)
|
|
34
|
+
export function isInScopeFile(file) {
|
|
35
|
+
if (file.startsWith('src/')) return true;
|
|
36
|
+
if (file.startsWith('scripts/')) return true;
|
|
37
|
+
if (file.startsWith('.husky/')) return true;
|
|
38
|
+
if (file.startsWith('skills/') && file.endsWith('SKILL.md')) return true;
|
|
39
|
+
if (file.startsWith('skills/') && (file.endsWith('.sh') || file.endsWith('.mjs') || file.endsWith('.js'))) return true;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isGateEvidence(change) {
|
|
44
|
+
// Post-#827 per-entry file (added per gate evaluation)…
|
|
45
|
+
if (/^\.instar\/instar-dev-decisions\/.+\.json$/.test(change.file)) return true;
|
|
46
|
+
// …or the pre-#827 legacy JSONL append (transition grace for in-flight PRs).
|
|
47
|
+
if (change.file === '.instar/instar-dev-decisions.jsonl') return true;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Pure evaluation — exported for unit tests.
|
|
53
|
+
* @param {{ changes: Array<{status: string, file: string}>, title?: string, authorType?: string }} input
|
|
54
|
+
* @returns {{ ok: boolean, exempt?: string, reason?: string, inScopeFiles?: string[] }}
|
|
55
|
+
*/
|
|
56
|
+
export function evaluateDecisionAuditPresence(input) {
|
|
57
|
+
const title = String(input?.title ?? '');
|
|
58
|
+
if (String(input?.authorType ?? '') === 'Bot') return { ok: true, exempt: 'bot-author' };
|
|
59
|
+
if (/^chore:\s*release\b/i.test(title)) return { ok: true, exempt: 'release-cut' };
|
|
60
|
+
|
|
61
|
+
const changes = Array.isArray(input?.changes) ? input.changes : [];
|
|
62
|
+
const inScopeFiles = changes.map((c) => c.file).filter(isInScopeFile);
|
|
63
|
+
if (inScopeFiles.length === 0) {
|
|
64
|
+
return { ok: true, reason: 'no in-scope changes — local gate not required' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const hasEvidence = changes.some(isGateEvidence);
|
|
68
|
+
if (hasEvidence) {
|
|
69
|
+
return { ok: true, reason: 'gate evidence present', inScopeFiles };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
inScopeFiles,
|
|
75
|
+
reason:
|
|
76
|
+
`This PR changes ${inScopeFiles.length} in-scope file(s) but carries NO decision-audit record — ` +
|
|
77
|
+
`the local instar-dev pre-commit gate did not run for these commits. The usual cause is a build ` +
|
|
78
|
+
`worktree without the husky shim (created with raw 'git worktree add' instead of 'instar worktree ` +
|
|
79
|
+
`create'). Fix: in the worktree run 'npm run prepare' (wires .husky/_), then re-commit so the gate ` +
|
|
80
|
+
`evaluates the change and its audit entry (.instar/instar-dev-decisions/<ts>-<slug>.json) rides the ` +
|
|
81
|
+
`commit. See tasks #81/#80 and PRs #827/#829.`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Parse `git diff --name-status base...head` output into change records. */
|
|
86
|
+
export function parseNameStatus(text) {
|
|
87
|
+
const changes = [];
|
|
88
|
+
for (const line of String(text).split('\n')) {
|
|
89
|
+
const t = line.trim();
|
|
90
|
+
if (!t) continue;
|
|
91
|
+
// Format: "<STATUS>\t<path>" (renames: "R100\t<old>\t<new>" — take the new path).
|
|
92
|
+
const parts = t.split('\t');
|
|
93
|
+
if (parts.length < 2) continue;
|
|
94
|
+
const status = parts[0];
|
|
95
|
+
const file = parts[parts.length - 1];
|
|
96
|
+
changes.push({ status, file });
|
|
97
|
+
}
|
|
98
|
+
return changes;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ── CLI entrypoint (CI) ────────────────────────────────────────────────────
|
|
102
|
+
const invokedDirectly = process.argv[1] && import.meta.url.endsWith(process.argv[1].split('/').pop());
|
|
103
|
+
if (invokedDirectly) {
|
|
104
|
+
const baseSha = process.env.BASE_SHA;
|
|
105
|
+
const headSha = process.env.HEAD_SHA;
|
|
106
|
+
if (!baseSha || !headSha) {
|
|
107
|
+
console.error('decision-audit gate: BASE_SHA and HEAD_SHA env vars are required.');
|
|
108
|
+
process.exit(2);
|
|
109
|
+
}
|
|
110
|
+
let diffOut;
|
|
111
|
+
try {
|
|
112
|
+
diffOut = execFileSync('git', ['diff', '--name-status', `${baseSha}...${headSha}`], { encoding: 'utf8' });
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.error(`decision-audit gate: git diff failed — ${err instanceof Error ? err.message : String(err)}`);
|
|
115
|
+
process.exit(2);
|
|
116
|
+
}
|
|
117
|
+
const res = evaluateDecisionAuditPresence({
|
|
118
|
+
changes: parseNameStatus(diffOut),
|
|
119
|
+
title: process.env.PR_TITLE,
|
|
120
|
+
authorType: process.env.PR_AUTHOR_TYPE,
|
|
121
|
+
});
|
|
122
|
+
if (res.ok) {
|
|
123
|
+
console.log(`decision-audit gate: OK — ${res.exempt ? `exempt (${res.exempt})` : res.reason}`);
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
console.error('decision-audit gate: FAIL');
|
|
127
|
+
console.error(res.reason);
|
|
128
|
+
console.error('');
|
|
129
|
+
console.error('In-scope files without gate evidence:');
|
|
130
|
+
for (const f of res.inScopeFiles ?? []) console.error(` - ${f}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-05T13:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-05T13:18:11.152Z",
|
|
5
|
+
"instarVersion": "1.3.297",
|
|
6
6
|
"entryCount": 198,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
New `scripts/decision-audit-presence-check.mjs` (pure evaluator + CLI) and `.github/workflows/decision-audit-gate.yml`. The in-scope predicate mirrors `scripts/instar-dev-precommit.js`.
|
|
9
|
+
|
|
10
|
+
## What to Tell Your User
|
|
11
|
+
|
|
12
|
+
Nothing user-visible — contributor CI. Pull requests that change gate-scoped code now fail fast if the local pre-commit gate never ran for them (the silent-bypass case), with a message that says exactly how to fix it.
|
|
13
|
+
|
|
14
|
+
## Summary of New Capabilities
|
|
15
|
+
|
|
16
|
+
- `decision-audit-gate` CI check: a PR touching src/scripts/.husky/skills code must include a decision-audit record (per-entry file post-#827, or a legacy jsonl modification as transition grace). Bot + release-cut PRs exempt.
|
|
17
|
+
- `.gitignore` now ignores a symlinked `node_modules` too (trailing-slash patterns only match real directories).
|
|
18
|
+
|
|
19
|
+
## Evidence
|
|
20
|
+
|
|
21
|
+
Live bypass this detects (2026-06-05): three build worktrees created with raw `git worktree add` had no husky shim — `git commit` ran zero hooks, so a full night's worktree commits carried no decision-audit records and nothing noticed (root fixed in #829; this is the structural backstop). Pinned by `tests/unit/decision-audit-presence-check.test.ts` (9 tests: scope predicate both sides, name-status parsing incl. renames, per-entry evidence passes, legacy-grace passes, the bypass shape FAILS with the actionable husky/`npm run prepare` message, docs-only passes, bot/release exemptions). CLI self-test green on an empty diff.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# A new PR check catches commits that skipped the local safety gate
|
|
2
|
+
|
|
3
|
+
Every code commit in this repo is supposed to pass through a local pre-commit gate that runs lint, tier checks, and writes a small audit record of the decision. That gate is wired up by husky, a tool that activates git hooks when dependencies are installed. We discovered (the hard way, across three build folders in one night) that folders created with raw git commands never get that wiring — so commits sail through with zero local checks and no audit record, and nothing anywhere notices. CI still re-runs the big checks on the pull request, but the gate's own protections (tier floors, deferral detection, the audit trail) silently never happened.
|
|
4
|
+
|
|
5
|
+
The root cause in the worktree-creation command is fixed separately. This change adds the structural backstop: a pull-request check that looks at what the PR changes. If it touches gate-scoped code (src, scripts, hooks, skills) it must ALSO carry the gate's audit record — the small decision file the gate stages into every commit it evaluates. A code PR with no record means the gate never ran for those commits, and the check fails with a message that names the likely cause (a worktree without hook wiring) and the exact fix (run `npm run prepare`, then re-commit). Docs-only PRs, bot PRs, and the automated release PR are unaffected, and PRs from before the audit-format change are accepted via their old-format record. Also fixes the `.gitignore` pattern so a symlinked `node_modules` can't be accidentally committed (a slip this very investigation made once).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Side-Effects Review — decision-audit presence PR gate (task #81 close-out)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `decision-audit-presence`
|
|
4
|
+
**Date:** `2026-06-05`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `self-review under the Tier-1 lite lane; over-block analysis below covers every legitimate-PR shape considered`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
New PR-boundary check (`scripts/decision-audit-presence-check.mjs` + `.github/workflows/decision-audit-gate.yml`): a PR whose diff touches gate-scoped files (src/, scripts/, .husky/, skills/ code — the same `inScope` predicate as the pre-commit gate) must also carry gate evidence: an added `.instar/instar-dev-decisions/*.json` per-entry file (post-#827) or a modified legacy `.instar/instar-dev-decisions.jsonl` (transition grace). Otherwise it fails with an actionable message naming the husky-shim cause and the `npm run prepare` fix. Plus: `.gitignore` `node_modules/` → `node_modules` so the worktree node_modules SYMLINK is ignored too (trailing-slash patterns match only directories; the symlink slipped into a commit once tonight).
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `evaluateDecisionAuditPresence` — add — pure; exemptions mirror the eli16 PR gate (Bot authors, `chore: release` titles).
|
|
15
|
+
- `isInScopeFile` — add — duplicated from `scripts/instar-dev-precommit.js` `inScope()` with a sync comment; drift only weakens detection, never blocks a PR the local gate doesn't also cover.
|
|
16
|
+
- Workflow — add — `pull_request` types opened/synchronize/reopened/ready_for_review, `contents: read`, full-depth checkout for the three-dot diff.
|
|
17
|
+
- `.gitignore` — modified — one-line pattern fix.
|
|
18
|
+
|
|
19
|
+
## 1. Over-block analysis (can this fail a legitimate PR?)
|
|
20
|
+
|
|
21
|
+
- **Docs/tests/upgrades-only PRs** — pass (no in-scope files).
|
|
22
|
+
- **In-flight pre-#827 PRs** — pass via the legacy-jsonl grace path (their gate appended a line).
|
|
23
|
+
- **Gated code PRs** — pass: the gate stages an audit record into every evaluated commit (post-#814), so evidence is present by construction.
|
|
24
|
+
- **The genuinely-blocked shape** — in-scope changes with NO record — is exactly the bypass this exists to surface. The failure message gives the one-command fix.
|
|
25
|
+
- **Bots + release-cut** — exempt, mirroring the eli16 gate.
|
|
26
|
+
- **Squash-merge interaction** — none: the check runs on the PR diff (base...head), not on merged history.
|
|
27
|
+
- Residual risk: an operator hand-editing in-scope files via the GitHub web UI would fail the check — correctly, since the gate never evaluated that change; the fix message applies (commit locally through the gate).
|
|
28
|
+
|
|
29
|
+
## 2. Under-block
|
|
30
|
+
|
|
31
|
+
Evidence is presence-based, not per-commit: one gated commit in a PR vouches for the PR even if a sibling commit bypassed. Accepted for this slice — the common failure mode (a WHOLE worktree without hooks) produces zero evidence and is caught. Per-commit matching would need trace↔commit correlation; noted as a possible tightening, not promised.
|
|
32
|
+
|
|
33
|
+
## 3. Signal vs authority
|
|
34
|
+
|
|
35
|
+
Deterministic file-presence check — no LLM, no semantic classification. Authority shape matches existing required CI (eli16 gate, repo invariants).
|
|
36
|
+
|
|
37
|
+
## 4. Migration parity
|
|
38
|
+
|
|
39
|
+
None — repo CI infrastructure, not agent-installed files.
|
|
40
|
+
|
|
41
|
+
## 5. Token/cost impact
|
|
42
|
+
|
|
43
|
+
One ubuntu CI job per PR event, seconds long. No LLM calls.
|
|
44
|
+
|
|
45
|
+
## 6. Rollback
|
|
46
|
+
|
|
47
|
+
Delete the workflow file (or revert); the script is inert without it.
|