instar 1.2.42 → 1.2.44
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/skills/build/SKILL.md +78 -8
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +51 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +15 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +197 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SafeFsExecutor.d.ts +18 -0
- package/dist/core/SafeFsExecutor.d.ts.map +1 -1
- package/dist/core/SafeFsExecutor.js +68 -0
- package/dist/core/SafeFsExecutor.js.map +1 -1
- package/package.json +1 -1
- package/scripts/check-e2e-pairing.cjs +102 -0
- package/src/data/builtin-manifest.json +19 -19
- package/src/templates/hooks/settings-template.json +5 -0
- package/upgrades/1.2.44.md +32 -0
- package/upgrades/side-effects/atomic-write-helper.md +30 -0
- package/upgrades/side-effects/build-methodology-bundle.md +34 -0
- package/upgrades/side-effects/e2e-pairing-gate.md +30 -0
- package/upgrades/side-effects/slopcheck-guard.md +36 -0
- package/upgrades/side-effects/verify-claim-skill.md +33 -0
|
@@ -129,6 +129,19 @@ export declare class PostUpdateMigrator {
|
|
|
129
129
|
* skills are never modified.
|
|
130
130
|
*/
|
|
131
131
|
private migrateSkillPortHardcoding;
|
|
132
|
+
/**
|
|
133
|
+
* Update the /build skill with the GSD-cherry-pick methodology sections
|
|
134
|
+
* (Phase 0.5 must-haves + STRIDE, atomic-commit discipline, SUMMARY
|
|
135
|
+
* deviation-tracking). installBuildSkill is install-if-missing, so existing
|
|
136
|
+
* agents need an explicit content-update migration.
|
|
137
|
+
*
|
|
138
|
+
* Idempotent + conservative: only re-copies the bundled SKILL.md when the
|
|
139
|
+
* installed copy (a) lacks the new "Phase 0.5: MUST-HAVES" marker AND
|
|
140
|
+
* (b) still looks like the stock /build skill (contains "Rigorous Build
|
|
141
|
+
* Skill" + "Phase 5: COMPLETE"). A heavily-customized /build skill that no
|
|
142
|
+
* longer matches the stock fingerprint is left untouched.
|
|
143
|
+
*/
|
|
144
|
+
private migrateBuildSkillMethodology;
|
|
132
145
|
/**
|
|
133
146
|
* Deploy any missing built-in skills (e.g., guardian job skills added after initial setup).
|
|
134
147
|
* Non-destructive — only writes SKILL.md files that don't already exist.
|
|
@@ -389,7 +402,7 @@ export declare class PostUpdateMigrator {
|
|
|
389
402
|
* Get the content of a named hook template.
|
|
390
403
|
* Used by init.ts to share canonical hook content without duplication.
|
|
391
404
|
*/
|
|
392
|
-
getHookContent(name: 'session-start' | 'compaction-recovery' | 'external-operation-gate' | 'deferral-detector' | 'post-action-reflection' | 'external-communication-guard' | 'scope-coherence-collector' | 'scope-coherence-checkpoint' | 'claim-intercept' | 'claim-intercept-response' | 'telegram-topic-context' | 'response-review' | 'auto-approve-permissions' | 'skill-usage-telemetry' | 'build-stop-hook'): string;
|
|
405
|
+
getHookContent(name: 'session-start' | 'compaction-recovery' | 'external-operation-gate' | 'deferral-detector' | 'slopcheck-guard' | 'post-action-reflection' | 'external-communication-guard' | 'scope-coherence-collector' | 'scope-coherence-checkpoint' | 'claim-intercept' | 'claim-intercept-response' | 'telegram-topic-context' | 'response-review' | 'auto-approve-permissions' | 'skill-usage-telemetry' | 'build-stop-hook'): string;
|
|
393
406
|
/** Public accessor for grounding-before-messaging hook content (used by init.ts) */
|
|
394
407
|
getGroundingBeforeMessagingPublic(): string;
|
|
395
408
|
/** Public accessor for convergence-check script content (used by init.ts) */
|
|
@@ -401,6 +414,7 @@ export declare class PostUpdateMigrator {
|
|
|
401
414
|
private getTelegramTopicContextHook;
|
|
402
415
|
private getCompactionRecovery;
|
|
403
416
|
private getDeferralDetectorHook;
|
|
417
|
+
private getSlopcheckGuardHook;
|
|
404
418
|
private getPostActionReflectionHook;
|
|
405
419
|
private getExternalCommunicationGuardHook;
|
|
406
420
|
private getExternalOperationGateHook;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PostUpdateMigrator.d.ts","sourceRoot":"","sources":["../../src/core/PostUpdateMigrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAiCH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAIjC,MAAM,WAAW,eAAe;IAC9B,wBAAwB;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,kCAAkC;IAClC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAiB;IAC/B;;;;;;OAMG;IACH,OAAO,CAAC,UAAU,CAAiC;gBAEvC,MAAM,EAAE,cAAc;IAIlC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAItC;;;;;;OAMG;IACG,eAAe,CACnB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,qBAAqB,CAAC;IAIjC,OAAO,CAAC,aAAa;IAOrB;;;OAGG;IACH,OAAO,IAAI,eAAe;
|
|
1
|
+
{"version":3,"file":"PostUpdateMigrator.d.ts","sourceRoot":"","sources":["../../src/core/PostUpdateMigrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAiCH,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAIjC,MAAM,WAAW,eAAe;IAC9B,wBAAwB;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,kCAAkC;IAClC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2CAA2C;IAC3C,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAiB;IAC/B;;;;;;OAMG;IACH,OAAO,CAAC,UAAU,CAAiC;gBAEvC,MAAM,EAAE,cAAc;IAIlC;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAItC;;;;;;OAMG;IACG,eAAe,CACnB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,qBAAqB,CAAC;IAIjC,OAAO,CAAC,aAAa;IAOrB;;;OAGG;IACH,OAAO,IAAI,eAAe;IA4E1B,OAAO,CAAC,0BAA0B;IAmDlC,OAAO,CAAC,uBAAuB;IAwE/B,OAAO,CAAC,4CAA4C;IAmEpD,OAAO,CAAC,yBAAyB;IAqGjC;;;;;;;;;;OAUG;IACG,YAAY,IAAI,OAAO,CAAC,eAAe,CAAC;YA4BhC,uBAAuB;IAkGrC,OAAO,CAAC,0BAA0B;IAkGlC,OAAO,CAAC,0BAA0B;IAkElC,OAAO,CAAC,oBAAoB;IAqH5B,OAAO,CAAC,8BAA8B;IA2EtC;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAaxB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,0BAA0B;IA8BlC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,4BAA4B;IAwBpC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAW5B;;;;;;;;;OASG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,OAAO,CAAC,yBAAyB;IA4HjC;6EACyE;IACzE,OAAO,CAAC,wBAAwB;IAShC;sDACkD;IAClD,OAAO,CAAC,wBAAwB;IAQhC;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAwKpB;;;;;;;;OAQG;IACH,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI;IA6CvE;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAgEzB;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAkChC;;;;;;;;;OASG;IACH,OAAO,CAAC,wBAAwB;IAoEhC;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAiE5B;;;;;OAKG;IACH,OAAO,CAAC,2BAA2B;IA8BnC;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IAwGhC;;;;;;OAMG;IACH,OAAO,CAAC,8BAA8B;IAwDtC,OAAO,CAAC,0BAA0B;IA8DlC;;;OAGG;IACH,OAAO,CAAC,eAAe;IAqjBvB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,kCAAkC;IA+E1C;;;OAGG;IACH,OAAO,CAAC,cAAc;IA6ItB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAqQvB;;;OAGG;IACH,OAAO,CAAC,aAAa;IAqFrB;;;OAGG;IACH;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAqChC,OAAO,CAAC,gBAAgB;IAiBxB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,OAAO,CAAC,qBAAqB;IAkE7B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,0BAA0B;IAgDlC;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAkC5B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,kBAAkB;IA2C1B;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,oBAAoB;IAgC5B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAqC9B;;;OAGG;IACH,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,qBAAqB,GAAG,yBAAyB,GAAG,mBAAmB,GAAG,iBAAiB,GAAG,wBAAwB,GAAG,8BAA8B,GAAG,2BAA2B,GAAG,4BAA4B,GAAG,iBAAiB,GAAG,0BAA0B,GAAG,wBAAwB,GAAG,iBAAiB,GAAG,0BAA0B,GAAG,uBAAuB,GAAG,iBAAiB,GAAG,MAAM;IAqB/a,oFAAoF;IACpF,iCAAiC,IAAI,MAAM;IAI3C,6EAA6E;IAC7E,yBAAyB,IAAI,MAAM;IAInC,OAAO,CAAC,mBAAmB;IAoY3B,OAAO,CAAC,wBAAwB;IAmEhC,OAAO,CAAC,2BAA2B;IA0DnC,OAAO,CAAC,yBAAyB;IAuGjC,OAAO,CAAC,2BAA2B;IAqInC,OAAO,CAAC,qBAAqB;IAqP7B,OAAO,CAAC,uBAAuB;IA6H/B,OAAO,CAAC,qBAAqB;IAsH7B,OAAO,CAAC,2BAA2B;IA8GnC,OAAO,CAAC,iCAAiC;IA6DzC,OAAO,CAAC,4BAA4B;IA4LpC;;;;;;;;;;OAUG;IACH;;;;;;;;;;;OAWG;IAEH,gBAAuB,iCAAiC,EAAE,WAAW,CAAC,MAAM,CAAC,CA4B1E;IAEH;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,8BAA8B;IAiHtC,OAAO,CAAC,uBAAuB;IA4B/B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,sBAAsB;IAwC9B,OAAO,CAAC,iBAAiB;IAwBzB,OAAO,CAAC,mBAAmB;IAoB3B,OAAO,CAAC,8BAA8B;IAoHtC,OAAO,CAAC,+BAA+B;IAmJvC,OAAO,CAAC,oBAAoB;IAO5B,OAAO,CAAC,qBAAqB;IA4N7B,OAAO,CAAC,qBAAqB;IA4H7B,OAAO,CAAC,6BAA6B;IAyKrC,OAAO,CAAC,0BAA0B;IAgClC,OAAO,CAAC,gBAAgB;IAqFxB,OAAO,CAAC,6BAA6B;CAoCtC"}
|
|
@@ -140,6 +140,7 @@ export class PostUpdateMigrator {
|
|
|
140
140
|
this.migrateBuiltinJobs(result);
|
|
141
141
|
this.autoMigrateLegacyJobsJson(result);
|
|
142
142
|
this.migrateSkillPortHardcoding(result);
|
|
143
|
+
this.migrateBuildSkillMethodology(result);
|
|
143
144
|
this.migrateSelfKnowledgeTree(result);
|
|
144
145
|
this.migrateSoulMd(result);
|
|
145
146
|
this.migrateAgentMdSections(result);
|
|
@@ -1016,6 +1017,45 @@ export class PostUpdateMigrator {
|
|
|
1016
1017
|
}
|
|
1017
1018
|
}
|
|
1018
1019
|
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Update the /build skill with the GSD-cherry-pick methodology sections
|
|
1022
|
+
* (Phase 0.5 must-haves + STRIDE, atomic-commit discipline, SUMMARY
|
|
1023
|
+
* deviation-tracking). installBuildSkill is install-if-missing, so existing
|
|
1024
|
+
* agents need an explicit content-update migration.
|
|
1025
|
+
*
|
|
1026
|
+
* Idempotent + conservative: only re-copies the bundled SKILL.md when the
|
|
1027
|
+
* installed copy (a) lacks the new "Phase 0.5: MUST-HAVES" marker AND
|
|
1028
|
+
* (b) still looks like the stock /build skill (contains "Rigorous Build
|
|
1029
|
+
* Skill" + "Phase 5: COMPLETE"). A heavily-customized /build skill that no
|
|
1030
|
+
* longer matches the stock fingerprint is left untouched.
|
|
1031
|
+
*/
|
|
1032
|
+
migrateBuildSkillMethodology(result) {
|
|
1033
|
+
try {
|
|
1034
|
+
const skillFile = path.join(this.config.projectDir, '.claude', 'skills', 'build', 'SKILL.md');
|
|
1035
|
+
if (!fs.existsSync(skillFile))
|
|
1036
|
+
return; // installBuildSkill handles fresh installs
|
|
1037
|
+
const current = fs.readFileSync(skillFile, 'utf8');
|
|
1038
|
+
if (current.includes('Phase 0.5: MUST-HAVES'))
|
|
1039
|
+
return; // already updated — idempotent
|
|
1040
|
+
// Stock-fingerprint guard: don't clobber a customized /build skill.
|
|
1041
|
+
if (!current.includes('Rigorous Build Skill') || !current.includes('Phase 5: COMPLETE')) {
|
|
1042
|
+
result.skipped.push('skills/build/SKILL.md: customized — left untouched (no methodology update)');
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1045
|
+
// Re-copy the bundled SKILL.md (which carries the new sections).
|
|
1046
|
+
const bundled = path.join(__dirname, '..', '..', '.claude', 'skills', 'build', 'SKILL.md');
|
|
1047
|
+
if (!fs.existsSync(bundled))
|
|
1048
|
+
return;
|
|
1049
|
+
const next = fs.readFileSync(bundled, 'utf8');
|
|
1050
|
+
if (next.includes('Phase 0.5: MUST-HAVES')) {
|
|
1051
|
+
fs.writeFileSync(skillFile, next);
|
|
1052
|
+
result.upgraded.push('skills/build/SKILL.md (GSD methodology: must-haves + atomic-commit + SUMMARY deviations)');
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
catch (err) {
|
|
1056
|
+
result.errors.push(`skills/build/SKILL.md methodology migration: ${err instanceof Error ? err.message : String(err)}`);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1019
1059
|
/**
|
|
1020
1060
|
* Deploy any missing built-in skills (e.g., guardian job skills added after initial setup).
|
|
1021
1061
|
* Non-destructive — only writes SKILL.md files that don't already exist.
|
|
@@ -1298,6 +1338,13 @@ export class PostUpdateMigrator {
|
|
|
1298
1338
|
catch (err) {
|
|
1299
1339
|
result.errors.push(`deferral-detector.js: ${err instanceof Error ? err.message : String(err)}`);
|
|
1300
1340
|
}
|
|
1341
|
+
try {
|
|
1342
|
+
fs.writeFileSync(path.join(instarHooksDir, 'slopcheck-guard.js'), this.getSlopcheckGuardHook(), { mode: 0o755 });
|
|
1343
|
+
result.upgraded.push('hooks/instar/slopcheck-guard.js (package-legitimacy check on installs)');
|
|
1344
|
+
}
|
|
1345
|
+
catch (err) {
|
|
1346
|
+
result.errors.push(`slopcheck-guard.js: ${err instanceof Error ? err.message : String(err)}`);
|
|
1347
|
+
}
|
|
1301
1348
|
try {
|
|
1302
1349
|
fs.writeFileSync(path.join(instarHooksDir, 'post-action-reflection.js'), this.getPostActionReflectionHook(), { mode: 0o755 });
|
|
1303
1350
|
result.upgraded.push('hooks/instar/post-action-reflection.js (evolution awareness)');
|
|
@@ -1463,6 +1510,7 @@ export class PostUpdateMigrator {
|
|
|
1463
1510
|
const builtinHooks = [
|
|
1464
1511
|
'session-start.sh', 'dangerous-command-guard.sh', 'grounding-before-messaging.sh',
|
|
1465
1512
|
'compaction-recovery.sh', 'external-operation-gate.js', 'deferral-detector.js',
|
|
1513
|
+
'slopcheck-guard.js',
|
|
1466
1514
|
'post-action-reflection.js', 'external-communication-guard.js',
|
|
1467
1515
|
'scope-coherence-collector.js', 'scope-coherence-checkpoint.js',
|
|
1468
1516
|
'instructions-loaded-tracker.js', 'subagent-start-tracker.js',
|
|
@@ -2883,6 +2931,37 @@ Create worktrees for collaborator repos with \`instar worktree create <branch>\`
|
|
|
2883
2931
|
result.upgraded.push('.claude/settings.json: migrated compaction hook from Notification to SessionStart');
|
|
2884
2932
|
}
|
|
2885
2933
|
}
|
|
2934
|
+
// Ensure PreToolUse Bash slopcheck-guard hook exists (cherry-pick 2026-05-23).
|
|
2935
|
+
// Existing agents only get new Bash-matcher hooks through an explicit ensure
|
|
2936
|
+
// block here — there is no wholesale settings-template refresh on migration.
|
|
2937
|
+
{
|
|
2938
|
+
const bashEntry = preToolUse.find(e => e.matcher === 'Bash');
|
|
2939
|
+
const hasSlopcheck = bashEntry?.hooks?.some(h => h.command?.includes('slopcheck-guard'));
|
|
2940
|
+
if (bashEntry && !hasSlopcheck) {
|
|
2941
|
+
bashEntry.hooks = bashEntry.hooks ?? [];
|
|
2942
|
+
bashEntry.hooks.push({
|
|
2943
|
+
type: 'command',
|
|
2944
|
+
command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/slopcheck-guard.js',
|
|
2945
|
+
timeout: 5000,
|
|
2946
|
+
});
|
|
2947
|
+
patched = true;
|
|
2948
|
+
result.upgraded.push('.claude/settings.json: added PreToolUse slopcheck-guard hook');
|
|
2949
|
+
}
|
|
2950
|
+
else if (!bashEntry) {
|
|
2951
|
+
// No Bash matcher at all — create one with just slopcheck (rare; most
|
|
2952
|
+
// agents already have a Bash matcher with dangerous-command-guard).
|
|
2953
|
+
preToolUse.push({
|
|
2954
|
+
matcher: 'Bash',
|
|
2955
|
+
hooks: [{
|
|
2956
|
+
type: 'command',
|
|
2957
|
+
command: 'node ${CLAUDE_PROJECT_DIR}/.instar/hooks/instar/slopcheck-guard.js',
|
|
2958
|
+
timeout: 5000,
|
|
2959
|
+
}],
|
|
2960
|
+
});
|
|
2961
|
+
patched = true;
|
|
2962
|
+
result.upgraded.push('.claude/settings.json: created PreToolUse Bash matcher with slopcheck-guard');
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2886
2965
|
// Ensure PostToolUse skill-usage-telemetry hook exists
|
|
2887
2966
|
{
|
|
2888
2967
|
const postToolUse = (hooks.PostToolUse || []);
|
|
@@ -3458,6 +3537,7 @@ Create worktrees for collaborator repos with \`instar worktree create <branch>\`
|
|
|
3458
3537
|
case 'compaction-recovery': return this.getCompactionRecovery();
|
|
3459
3538
|
case 'external-operation-gate': return this.getExternalOperationGateHook();
|
|
3460
3539
|
case 'deferral-detector': return this.getDeferralDetectorHook();
|
|
3540
|
+
case 'slopcheck-guard': return this.getSlopcheckGuardHook();
|
|
3461
3541
|
case 'post-action-reflection': return this.getPostActionReflectionHook();
|
|
3462
3542
|
case 'external-communication-guard': return this.getExternalCommunicationGuardHook();
|
|
3463
3543
|
case 'scope-coherence-collector': return this.getScopeCoherenceCollectorHook();
|
|
@@ -4589,6 +4669,123 @@ process.stdin.on('end', () => {
|
|
|
4589
4669
|
} catch { /* don't break on errors */ }
|
|
4590
4670
|
process.exit(0);
|
|
4591
4671
|
});
|
|
4672
|
+
`;
|
|
4673
|
+
}
|
|
4674
|
+
getSlopcheckGuardHook() {
|
|
4675
|
+
return `#!/usr/bin/env node
|
|
4676
|
+
// Slopcheck guard — package-legitimacy check on install commands.
|
|
4677
|
+
// PreToolUse hook for Bash. When the command is a package install
|
|
4678
|
+
// (npm/pnpm/yarn/pip/cargo), it extracts the package names and checks
|
|
4679
|
+
// whether each is already known to the project (present in a lockfile
|
|
4680
|
+
// or manifest). Unfamiliar packages get a confirmation nudge — does NOT
|
|
4681
|
+
// block, just surfaces the legitimacy question before a slopsquatted or
|
|
4682
|
+
// hallucinated package gets installed.
|
|
4683
|
+
//
|
|
4684
|
+
// Cherry-picked into Instar 2026-05-23 from the GSD-Instar integration
|
|
4685
|
+
// spike (gsd-executor Rule 3 exclusion: package installs are NOT
|
|
4686
|
+
// auto-fixable because a failed/typo'd install may be a slopsquat).
|
|
4687
|
+
// Signal-only — decision: approve + additionalContext. The agent decides.
|
|
4688
|
+
|
|
4689
|
+
const fs = require('fs');
|
|
4690
|
+
const path = require('path');
|
|
4691
|
+
|
|
4692
|
+
// Install-command patterns → capture the args portion after the verb.
|
|
4693
|
+
const INSTALL_PATTERNS = [
|
|
4694
|
+
{ re: /\\bnpm\\s+(?:i|install|add)\\s+([^&|;]+)/i, mgr: 'npm' },
|
|
4695
|
+
{ re: /\\bpnpm\\s+(?:i|install|add)\\s+([^&|;]+)/i, mgr: 'pnpm' },
|
|
4696
|
+
{ re: /\\byarn\\s+add\\s+([^&|;]+)/i, mgr: 'yarn' },
|
|
4697
|
+
{ re: /\\bpip3?\\s+install\\s+([^&|;]+)/i, mgr: 'pip' },
|
|
4698
|
+
{ re: /\\bcargo\\s+add\\s+([^&|;]+)/i, mgr: 'cargo' },
|
|
4699
|
+
];
|
|
4700
|
+
|
|
4701
|
+
// Flags to strip when extracting package names.
|
|
4702
|
+
const FLAG_RE = /^-/;
|
|
4703
|
+
|
|
4704
|
+
function parsePackages(argStr) {
|
|
4705
|
+
return argStr
|
|
4706
|
+
.trim()
|
|
4707
|
+
.split(/\\s+/)
|
|
4708
|
+
.filter(tok => tok && !FLAG_RE.test(tok))
|
|
4709
|
+
// Strip version specifiers: pkg@1.2.3, pkg==1.2.3, pkg~=1.0
|
|
4710
|
+
.map(tok => tok.replace(/[@=~<>!].*$/, '').replace(/\\[.*$/, ''))
|
|
4711
|
+
.filter(Boolean);
|
|
4712
|
+
}
|
|
4713
|
+
|
|
4714
|
+
// Returns the set of package names already known to the project from
|
|
4715
|
+
// any manifest/lockfile present in the project dir.
|
|
4716
|
+
function knownPackages(projectDir) {
|
|
4717
|
+
const known = new Set();
|
|
4718
|
+
const add = (name) => { if (name && typeof name === 'string') known.add(name.toLowerCase()); };
|
|
4719
|
+
|
|
4720
|
+
// package.json deps + devDeps + lock
|
|
4721
|
+
try {
|
|
4722
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), 'utf-8'));
|
|
4723
|
+
for (const k of ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies']) {
|
|
4724
|
+
if (pkg[k]) Object.keys(pkg[k]).forEach(add);
|
|
4725
|
+
}
|
|
4726
|
+
} catch { /* no package.json */ }
|
|
4727
|
+
// package-lock.json — names appear as keys; cheap substring presence check via raw read
|
|
4728
|
+
let lockRaw = '';
|
|
4729
|
+
for (const lf of ['package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'requirements.txt', 'Cargo.toml', 'Cargo.lock', 'Pipfile', 'pyproject.toml']) {
|
|
4730
|
+
try { lockRaw += '\\n' + fs.readFileSync(path.join(projectDir, lf), 'utf-8'); } catch { /* absent */ }
|
|
4731
|
+
}
|
|
4732
|
+
return { known, lockRaw: lockRaw.toLowerCase() };
|
|
4733
|
+
}
|
|
4734
|
+
|
|
4735
|
+
let data = '';
|
|
4736
|
+
process.stdin.on('data', chunk => data += chunk);
|
|
4737
|
+
process.stdin.on('end', () => {
|
|
4738
|
+
try {
|
|
4739
|
+
const input = JSON.parse(data);
|
|
4740
|
+
if (input.tool_name !== 'Bash') process.exit(0);
|
|
4741
|
+
const command = (input.tool_input || {}).command || '';
|
|
4742
|
+
if (!command) process.exit(0);
|
|
4743
|
+
|
|
4744
|
+
let matched = null;
|
|
4745
|
+
for (const p of INSTALL_PATTERNS) {
|
|
4746
|
+
const m = command.match(p.re);
|
|
4747
|
+
if (m) { matched = { mgr: p.mgr, args: m[1] }; break; }
|
|
4748
|
+
}
|
|
4749
|
+
if (!matched) process.exit(0);
|
|
4750
|
+
|
|
4751
|
+
const pkgs = parsePackages(matched.args);
|
|
4752
|
+
if (pkgs.length === 0) process.exit(0);
|
|
4753
|
+
|
|
4754
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
4755
|
+
const { known, lockRaw } = knownPackages(projectDir);
|
|
4756
|
+
|
|
4757
|
+
// A package is "familiar" if it's in the manifest deps OR appears as a
|
|
4758
|
+
// token in any lockfile (covers transitive + already-installed).
|
|
4759
|
+
const unfamiliar = pkgs.filter(pkg => {
|
|
4760
|
+
const low = pkg.toLowerCase();
|
|
4761
|
+
if (known.has(low)) return false;
|
|
4762
|
+
// Word-boundary-ish presence in lockfiles (quoted or pathed)
|
|
4763
|
+
if (lockRaw.includes('"' + low + '"') || lockRaw.includes('/' + low) ||
|
|
4764
|
+
lockRaw.includes(low + '@') || lockRaw.includes(low + ' ') || lockRaw.includes(low + '==')) return false;
|
|
4765
|
+
return true;
|
|
4766
|
+
});
|
|
4767
|
+
|
|
4768
|
+
if (unfamiliar.length === 0) process.exit(0);
|
|
4769
|
+
|
|
4770
|
+
const checklist = [
|
|
4771
|
+
'SLOPCHECK — installing package(s) not already known to this project: ' + unfamiliar.join(', '),
|
|
4772
|
+
'',
|
|
4773
|
+
'Before installing an unfamiliar package, confirm it is legitimate (not a',
|
|
4774
|
+
'slopsquat or a hallucinated name):',
|
|
4775
|
+
'',
|
|
4776
|
+
'1. Is the spelling exactly right? Typosquats differ by one or two characters.',
|
|
4777
|
+
'2. Does the package actually exist on its registry, with real download counts',
|
|
4778
|
+
' and a credible publisher? (npmjs.com / pypi.org / crates.io)',
|
|
4779
|
+
'3. Did YOU choose this package deliberately, or did it come from an LLM',
|
|
4780
|
+
' suggestion that might be hallucinated?',
|
|
4781
|
+
'4. Is there a more-established alternative you already trust?',
|
|
4782
|
+
'',
|
|
4783
|
+
'This is a nudge, not a block. If the package is legitimate, proceed.',
|
|
4784
|
+
];
|
|
4785
|
+
process.stdout.write(JSON.stringify({ decision: 'approve', additionalContext: checklist.join('\\n') }));
|
|
4786
|
+
} catch { /* never block on errors */ }
|
|
4787
|
+
process.exit(0);
|
|
4788
|
+
});
|
|
4592
4789
|
`;
|
|
4593
4790
|
}
|
|
4594
4791
|
getPostActionReflectionHook() {
|