greprag 5.74.10 → 5.74.12
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/dist/codex-delivery-coordination.js +213 -0
- package/dist/codex-fast-hook.js +5 -8
- package/dist/commands/coordinate-gate.js +2 -2
- package/dist/commands/delivery-reminder.js +8 -9
- package/dist/commands/doctor.js +7 -3
- package/dist/commands/friction-reminder.js +18 -42
- package/dist/commands/init.js +9 -18
- package/dist/commands/load.js +1 -1
- package/dist/commands/opencode-interrupt.js +3 -3
- package/dist/commands/os-primer-reminder.js +3 -2
- package/dist/commands/reminder-registry.js +3 -4
- package/dist/commands/status.js +6 -2
- package/dist/opencode-plugin.bundle.js +20 -38
- package/dist/windows-shims.js +19 -3
- package/package.json +1 -1
- package/skill/mechanic/SKILL.md +14 -11
- package/skill/mechanic/docs/skill-fix-conventions.md +1 -1
- package/skill/templates/os.md +15 -12
- package/dist/codex-checkpoint-hook.js +0 -184
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Codex delivery coordination - interrupt the first delivery action once,
|
|
3
|
+
* then keep the rest of that delivery run non-blocking.
|
|
4
|
+
*
|
|
5
|
+
* The hook cannot call native Codex task tools. It names the exact agent-owned
|
|
6
|
+
* discovery and messaging actions, persists the open delivery window before
|
|
7
|
+
* denying, and never waits for peer replies or attestation.
|
|
8
|
+
*
|
|
9
|
+
* adr: adr/codex-coordinate-gate.md */
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
exports.DELIVERY_COORDINATION_WINDOW_MS = void 0;
|
|
45
|
+
exports.codexDeliveryTrigger = codexDeliveryTrigger;
|
|
46
|
+
exports.codexDeliveryCoordinationText = codexDeliveryCoordinationText;
|
|
47
|
+
exports.evaluateCodexDeliveryCoordination = evaluateCodexDeliveryCoordination;
|
|
48
|
+
const crypto = __importStar(require("crypto"));
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const child_process_1 = require("child_process");
|
|
52
|
+
const coordinate_gate_1 = require("./commands/coordinate-gate");
|
|
53
|
+
exports.DELIVERY_COORDINATION_WINDOW_MS = 30 * 60 * 1000;
|
|
54
|
+
function homeDir() {
|
|
55
|
+
return process.env.USERPROFILE || process.env.HOME || '';
|
|
56
|
+
}
|
|
57
|
+
function normalize(value) {
|
|
58
|
+
return path.resolve(value).replace(/\\/g, '/').toLowerCase();
|
|
59
|
+
}
|
|
60
|
+
function gitRoot(cwd) {
|
|
61
|
+
try {
|
|
62
|
+
const root = (0, child_process_1.execFileSync)('git', ['rev-parse', '--show-toplevel'], {
|
|
63
|
+
cwd,
|
|
64
|
+
encoding: 'utf8',
|
|
65
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
66
|
+
windowsHide: true,
|
|
67
|
+
}).trim();
|
|
68
|
+
return root ? normalize(root) : null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function statePath(input, repoRoot) {
|
|
75
|
+
const home = homeDir();
|
|
76
|
+
const session = (input.session_id || '').trim();
|
|
77
|
+
if (!home || !session)
|
|
78
|
+
return null;
|
|
79
|
+
const key = crypto.createHash('sha256')
|
|
80
|
+
.update(`${session}\0${repoRoot}`)
|
|
81
|
+
.digest('hex')
|
|
82
|
+
.slice(0, 24);
|
|
83
|
+
return path.join(home, '.greprag', 'state', `codex-delivery-${key}.json`);
|
|
84
|
+
}
|
|
85
|
+
function readWindow(file) {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function writeWindow(file, window) {
|
|
94
|
+
try {
|
|
95
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
96
|
+
fs.writeFileSync(file, JSON.stringify(window, null, 2) + '\n');
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function isShellCapableTool(input) {
|
|
104
|
+
const name = (input.tool_name || '').toLowerCase();
|
|
105
|
+
return /(?:^|[.:/_-])(bash|shell|exec|exec_command)$/.test(name);
|
|
106
|
+
}
|
|
107
|
+
function decodeQuoted(value) {
|
|
108
|
+
if (value.startsWith('"')) {
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(value);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (value.startsWith("'")) {
|
|
117
|
+
return value.slice(1, -1).replace(/\\'/g, "'").replace(/\\\\/g, '\\');
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
function embeddedCommands(code) {
|
|
122
|
+
const commands = [];
|
|
123
|
+
const re = /(?:command|cmd)\s*:\s*("(?:\\.|[^"])*"|'(?:\\.|[^'])*')/g;
|
|
124
|
+
for (const match of code.matchAll(re)) {
|
|
125
|
+
const decoded = decodeQuoted(match[1]);
|
|
126
|
+
if (decoded)
|
|
127
|
+
commands.push(decoded);
|
|
128
|
+
}
|
|
129
|
+
return commands;
|
|
130
|
+
}
|
|
131
|
+
function commandCandidates(input) {
|
|
132
|
+
if (!isShellCapableTool(input))
|
|
133
|
+
return [];
|
|
134
|
+
const toolInput = input.tool_input;
|
|
135
|
+
if (typeof toolInput === 'string')
|
|
136
|
+
return embeddedCommands(toolInput);
|
|
137
|
+
if (!toolInput || typeof toolInput !== 'object')
|
|
138
|
+
return [];
|
|
139
|
+
const candidates = [];
|
|
140
|
+
const fields = toolInput;
|
|
141
|
+
for (const key of ['command', 'cmd', 'script']) {
|
|
142
|
+
const value = fields[key];
|
|
143
|
+
if (typeof value === 'string')
|
|
144
|
+
candidates.push(value);
|
|
145
|
+
}
|
|
146
|
+
for (const key of ['code', 'input']) {
|
|
147
|
+
const value = fields[key];
|
|
148
|
+
if (typeof value === 'string')
|
|
149
|
+
candidates.push(...embeddedCommands(value));
|
|
150
|
+
}
|
|
151
|
+
return candidates;
|
|
152
|
+
}
|
|
153
|
+
function codexDeliveryTrigger(input) {
|
|
154
|
+
for (const command of commandCandidates(input)) {
|
|
155
|
+
const trigger = (0, coordinate_gate_1.classifyRiskyCommand)(command);
|
|
156
|
+
if (trigger)
|
|
157
|
+
return trigger;
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
function codexDeliveryCoordinationText(trigger) {
|
|
162
|
+
return `[DELIVERY NOW]
|
|
163
|
+
|
|
164
|
+
Before ${trigger.label}, call codex_app.list_threads unfiltered and keep only peers in the same repo. Use codex_app.send_message_to_thread once for peers with ready or overlapping work; include your branch/commit and ask only for ready commits or concrete blockers.
|
|
165
|
+
|
|
166
|
+
Include ready handoffs and clear known blockers, then retry immediately. Do not call wait_threads, wait for replies, or send follow-ups. Missing replies and unfinished peer work do not block delivery.`;
|
|
167
|
+
}
|
|
168
|
+
/** First delivery action opens a 30-minute session/repo window and interrupts
|
|
169
|
+
* once. Later merge/push/deploy/release actions refresh the same window and
|
|
170
|
+
* proceed without another coordination round. */
|
|
171
|
+
function evaluateCodexDeliveryCoordination(input, now = Date.now()) {
|
|
172
|
+
if (input.hook_event_name !== 'PreToolUse')
|
|
173
|
+
return null;
|
|
174
|
+
const trigger = codexDeliveryTrigger(input);
|
|
175
|
+
if (!trigger)
|
|
176
|
+
return null;
|
|
177
|
+
const cwd = input.cwd || process.cwd();
|
|
178
|
+
const repoRoot = gitRoot(cwd);
|
|
179
|
+
if (!repoRoot)
|
|
180
|
+
return null;
|
|
181
|
+
const file = statePath(input, repoRoot);
|
|
182
|
+
if (!file)
|
|
183
|
+
return null;
|
|
184
|
+
const previous = readWindow(file);
|
|
185
|
+
const previousLastAction = previous ? Date.parse(previous.lastActionAt) : NaN;
|
|
186
|
+
if (previous
|
|
187
|
+
&& previous.repoRoot === repoRoot
|
|
188
|
+
&& Number.isFinite(previousLastAction)
|
|
189
|
+
&& now - previousLastAction < exports.DELIVERY_COORDINATION_WINDOW_MS) {
|
|
190
|
+
writeWindow(file, {
|
|
191
|
+
...previous,
|
|
192
|
+
lastActionAt: new Date(now).toISOString(),
|
|
193
|
+
});
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
const openedAt = new Date(now).toISOString();
|
|
197
|
+
if (!writeWindow(file, {
|
|
198
|
+
sessionId: input.session_id || '',
|
|
199
|
+
repoRoot,
|
|
200
|
+
openedAt,
|
|
201
|
+
lastActionAt: openedAt,
|
|
202
|
+
})) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
hookSpecificOutput: {
|
|
207
|
+
hookEventName: 'PreToolUse',
|
|
208
|
+
additionalContext: codexDeliveryCoordinationText(trigger),
|
|
209
|
+
permissionDecision: 'deny',
|
|
210
|
+
permissionDecisionReason: `Run one same-repo coordination pass, then retry ${trigger.label} immediately; peer replies are not required.`,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
package/dist/codex-fast-hook.js
CHANGED
|
@@ -83,10 +83,8 @@ async function main() {
|
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
85
|
if (subcommand === 'codex-posttooluse') {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (result)
|
|
89
|
-
process.stdout.write(JSON.stringify(result) + '\n');
|
|
86
|
+
// Compatibility no-op for installed configs predating the delivery-boundary
|
|
87
|
+
// cutover. `greprag init --codex` removes this hook.
|
|
90
88
|
return;
|
|
91
89
|
}
|
|
92
90
|
const { evaluateCodexChipHook } = await Promise.resolve().then(() => __importStar(require('./codex-chip-hooks')));
|
|
@@ -97,10 +95,9 @@ async function main() {
|
|
|
97
95
|
return;
|
|
98
96
|
}
|
|
99
97
|
let result = chipResult;
|
|
100
|
-
if (subcommand === 'codex-pretooluse') {
|
|
101
|
-
const {
|
|
102
|
-
result = mergeOutputs(result,
|
|
103
|
-
recordCodexGitBoundary(input);
|
|
98
|
+
if (subcommand === 'codex-pretooluse' && !result?.hookSpecificOutput.permissionDecision) {
|
|
99
|
+
const { evaluateCodexDeliveryCoordination } = await Promise.resolve().then(() => __importStar(require('./codex-delivery-coordination')));
|
|
100
|
+
result = mergeOutputs(result, evaluateCodexDeliveryCoordination(input));
|
|
104
101
|
}
|
|
105
102
|
if (!result?.hookSpecificOutput.permissionDecision && input.tool_name === 'Bash') {
|
|
106
103
|
const { runSearchGuard } = await Promise.resolve().then(() => __importStar(require('./commands/search-guard')));
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
* dedups to stay quiet), the merge gate must reflect live truth each time.
|
|
28
28
|
*
|
|
29
29
|
* EFFECT is the adapter's choice, not the eval's: Claude/OpenCode inject the
|
|
30
|
-
* fresh roster as context
|
|
31
|
-
*
|
|
30
|
+
* fresh roster as context. Codex uses the separate local, bounded delivery
|
|
31
|
+
* interrupt in codex-delivery-coordination.ts and carries no attestation. An
|
|
32
32
|
* ingress-source wrapper would inject instead. The eval below only produces
|
|
33
33
|
* the message.
|
|
34
34
|
*
|
|
@@ -12,27 +12,26 @@ exports.buildDeliveryAnnounce = buildDeliveryAnnounce;
|
|
|
12
12
|
// adr: adr/delivery-announce-pilot.md
|
|
13
13
|
function coordinationLine(platform) {
|
|
14
14
|
if (platform === 'codex') {
|
|
15
|
-
return 'Codex
|
|
15
|
+
return 'Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo, then use codex_app.send_message_to_thread once for ready or overlapping work.';
|
|
16
16
|
}
|
|
17
17
|
if (platform === 'claude-code') {
|
|
18
|
-
return 'Claude Code
|
|
18
|
+
return 'Claude Code: at the merge/deploy gate, discover peers in the same repo and send one concise delivery notice to peers with ready or overlapping work.';
|
|
19
19
|
}
|
|
20
20
|
if (platform === 'opencode') {
|
|
21
|
-
return 'OpenCode
|
|
21
|
+
return 'OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise delivery notice to peers with ready or overlapping work.';
|
|
22
22
|
}
|
|
23
|
-
return '
|
|
23
|
+
return 'At the merge/deploy gate, coordinate once with peers in the same repo who have ready or overlapping work.';
|
|
24
24
|
}
|
|
25
25
|
function buildDeliveryAnnounce(env) {
|
|
26
26
|
if (!env.deliveryRepo)
|
|
27
27
|
return null;
|
|
28
28
|
const project = env.projectName?.trim() || 'current repo';
|
|
29
29
|
return [
|
|
30
|
-
`[DELIVERY
|
|
31
|
-
'
|
|
30
|
+
`[DELIVERY — ${project}]`,
|
|
31
|
+
'Commit useful, passing work.',
|
|
32
32
|
coordinationLine(env.platform),
|
|
33
|
-
'
|
|
34
|
-
'Use
|
|
35
|
-
'A full-goal delivery mission already provisions ordinary repo-scoped commit, merge, push, deploy, and verification; do not wait for a second keyword.',
|
|
33
|
+
'Include ready peer commits and clear known blockers, then merge to the default branch and deploy immediately. Missing replies and unfinished peer work do not block delivery.',
|
|
34
|
+
'Use this repo\'s delivery profile and verify production.',
|
|
36
35
|
].join('\n');
|
|
37
36
|
}
|
|
38
37
|
exports.deliveryControlModule = {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -275,8 +275,12 @@ async function runShimAudit(opts) {
|
|
|
275
275
|
console.log('Windows shims:');
|
|
276
276
|
for (const s of shims) {
|
|
277
277
|
const ver = s.version ? `v${s.version}` : '(version unknown)';
|
|
278
|
-
const
|
|
279
|
-
|
|
278
|
+
const details = [
|
|
279
|
+
...(s.incomplete ? [`missing ${s.missingFiles.join(', ')}`] : []),
|
|
280
|
+
...(s.volatileForward ? ['fnm temp forward'] : []),
|
|
281
|
+
];
|
|
282
|
+
const suffix = details.length ? ` ${details.join(', ')}` : '';
|
|
283
|
+
const flag = s.stale ? ` ⚠ STALE${suffix}` : s.incomplete ? ` ⚠ INCOMPLETE${suffix}` : s.volatileForward ? ` ⚠ VOLATILE${suffix}` : s.unresolvable ? ' ? unresolvable' : '';
|
|
280
284
|
console.log(` ${s.name}${s.active ? '' : ' (shadowed)'}: ${s.shimPath} → ${ver}${flag}`);
|
|
281
285
|
}
|
|
282
286
|
if (unhealthy.length === 0) {
|
|
@@ -291,7 +295,7 @@ async function runShimAudit(opts) {
|
|
|
291
295
|
if (!opts.yes) {
|
|
292
296
|
const fixable = repairs.filter(r => r.forwardTo);
|
|
293
297
|
for (const r of repairs.filter(x => !x.forwardTo)) {
|
|
294
|
-
console.log(` ✗ ${r.shim.shimPath} is unhealthy and no current ${r.shim.name} shim exists on PATH to forward to — run
|
|
298
|
+
console.log(` ✗ ${r.shim.shimPath} is unhealthy and no durable current ${r.shim.name} shim exists on PATH to forward to — run \`${(0, windows_shims_1.shimInstallCommand)()}\` first.`);
|
|
295
299
|
}
|
|
296
300
|
if (fixable.length === 0) {
|
|
297
301
|
console.log('');
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* live stress level) rather than a binary resolvable deficiency.
|
|
6
6
|
*
|
|
7
7
|
* A reminder-interrupt MODULE is `{ id, detect, announce, reminder }` (reminder-types.ts):
|
|
8
|
-
* `detect(env)` reads the live state → a tier (`silent` = nothing to fix);
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* `detect(env)` reads the live state → a tier (`silent` = nothing to fix); the grepragOS
|
|
9
|
+
* primer owns the startup schema, and this module contributes only a thin live reminder.
|
|
10
|
+
* The container (reminder-registry.ts) wires every module; the hook (hook.ts) assembles
|
|
11
|
+
* the env and emits — see `injectReminders`.
|
|
12
12
|
*
|
|
13
13
|
* PURE: imports only the stress constants; returns strings + a tier + a tally. No fs, no
|
|
14
14
|
* network, no clock — the hook does ALL I/O. Keeping detect/tier a pure function is what
|
|
@@ -19,10 +19,10 @@ exports.frictionReminderTier = frictionReminderTier;
|
|
|
19
19
|
exports.buildMechanicLiveNotice = buildMechanicLiveNotice;
|
|
20
20
|
exports.buildFrictionReminder = buildFrictionReminder;
|
|
21
21
|
exports.buildBootstrapFrictionReminder = buildBootstrapFrictionReminder;
|
|
22
|
-
exports.buildMechanicAnnounce = buildMechanicAnnounce;
|
|
23
22
|
exports.tallyFrictionFire = tallyFrictionFire;
|
|
24
23
|
exports.tallyMechanicSpawn = tallyMechanicSpawn;
|
|
25
24
|
const state_trigger_1 = require("./state-trigger");
|
|
25
|
+
// adr: adr/friction-memory-first.md
|
|
26
26
|
function quoteForCommand(value) {
|
|
27
27
|
return value.replace(/[\r\n]+/g, ' ').replace(/"/g, '\\"').trim();
|
|
28
28
|
}
|
|
@@ -49,21 +49,22 @@ function frictionReminderTier(stress) {
|
|
|
49
49
|
return 'nudge';
|
|
50
50
|
return 'silent';
|
|
51
51
|
}
|
|
52
|
-
/** The per-turn reminder line — fires only on detected stress, points at
|
|
53
|
-
*
|
|
52
|
+
/** The per-turn reminder line — fires only on detected stress, points first at
|
|
53
|
+
* Memory for a known overcome, then at fix spawn for unresolved friction.
|
|
54
|
+
* Ends on a forced binary so it converts. null = calm.
|
|
54
55
|
* The queue-route (`mechanic friction`) and log-route (`fix log`) verbs were
|
|
55
|
-
* RETIRED as reflexes 2026-07-12 (grepragOS)
|
|
56
|
-
* one chip per unit. */
|
|
56
|
+
* RETIRED as reflexes 2026-07-12 (grepragOS). */
|
|
57
57
|
const FIX_SPAWN = 'greprag fix spawn --type <harness|doctrine|injection|env|code> "<one unit>"';
|
|
58
|
+
const MEMORY_SEARCH = 'greprag memory search "<exact error/friction + repo/tool>"';
|
|
58
59
|
function buildMechanicLiveNotice(_env) {
|
|
59
|
-
return `GrepRAG Mechanic is live. Friction
|
|
60
|
+
return `GrepRAG Mechanic is live. Friction order: \`${MEMORY_SEARCH}\` first; apply a clear overcome and continue. No answer, failed overcome, or repeated friction → \`${FIX_SPAWN}\`. Check role only when needed: \`greprag mechanic role\`.`;
|
|
60
61
|
}
|
|
61
62
|
function buildFrictionReminder(tier, env) {
|
|
62
63
|
switch (tier) {
|
|
63
64
|
case 'nag':
|
|
64
|
-
return `⚠ FRICTION HIGH (live signal: repetition / errors / churn) — STOP pushing through.
|
|
65
|
+
return `⚠ FRICTION HIGH (live signal: repetition / errors / churn) — STOP pushing through. MEMORY FIRST: \`${MEMORY_SEARCH}\`. Clear overcome → apply + continue. No answer, remembered overcome fails, or same friction repeats → \`${FIX_SPAWN}\`. No friction? say "clear" + continue.`;
|
|
65
66
|
case 'nudge':
|
|
66
|
-
return `⚠ Friction detected (live signal) — repeated yourself, fought a tool, or hit a wall?
|
|
67
|
+
return `⚠ Friction detected (live signal) — repeated yourself, fought a tool, or hit a wall? Search first: \`${MEMORY_SEARCH}\`. Apply a clear overcome + continue; no answer, failed overcome, or repeat → \`${FIX_SPAWN}\`. Otherwise say "clear" + continue.`;
|
|
67
68
|
default:
|
|
68
69
|
return null;
|
|
69
70
|
}
|
|
@@ -74,25 +75,8 @@ function buildBootstrapFrictionReminder(env) {
|
|
|
74
75
|
return null;
|
|
75
76
|
const repeats = b.repeats && b.repeats > 1 ? ` repeated ${b.repeats}x` : '';
|
|
76
77
|
const error = b.error ? ` Last error: ${b.error}` : '';
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
// ---------- The fix-reflex primer (announce-only) ----------------------------
|
|
80
|
-
/** SessionStart primer — the fix reflex, loaded ONCE. Full doctrine inline
|
|
81
|
-
* (a PRIMER, not a pointer): it teaches WHEN friction is a spawn-now signal
|
|
82
|
-
* and the chip's delivery-owner contract. Rewritten 2026-07-12 (grepragOS):
|
|
83
|
-
* the queue-route (`mechanic friction`), log-route (`fix log`) and the
|
|
84
|
-
* one-Mechanic-absorbs-all-friction model are RETIRED as reflexes — one unit
|
|
85
|
-
* of friction ⇒ one fix chip, spawned at the moment. */
|
|
86
|
-
function buildMechanicAnnounce() {
|
|
87
|
-
return [
|
|
88
|
-
'[Fix reflex — friction is fixed at the MOMENT it happens, one chip per unit (grepragOS law; full doctrine: `greprag load os`).]',
|
|
89
|
-
'WHEN friction happens — repeated yourself, fought a tool, got corrected >=2x on the same thing, rediscovered something already known, or hit a setup/toolchain failure not caused by code you just edited — spawn its fix chip NOW, then keep working. Ordinary misunderstanding or misreading user intent is not friction unless it repeats or exposes a durable doctrine/harness/injection/env/code failure. One unit = one chip; adjacent friction gets its own spawn. Never queue friction for later.',
|
|
90
|
-
'TYPE = durable repair surface: harness=hooks/watchers/task dispatch; doctrine=greprag load/skills/AGENTS; injection=recap/Capture/doc-pointer/stateful injection; env=bootstrap/deps/scripts/worktree setup; code=product/source behavior. Use: `greprag fix spawn --type <type> "<one unit>"`.',
|
|
91
|
-
'WORKSPACE ROUTING: `fix spawn` detects usable Git history before dispatch — Git uses an isolated worktree; non-Git, unavailable Git, or no commit uses the project-local task with serialized writes. No fail-then-fallback attempt.',
|
|
92
|
-
'THE CHIP\'S CONTRACT (it self-enforces; you just spawn): identify the exact friction → make the smallest durable root-cause fix → explain and verify it → checkpoint → hand it to the mission delivery owner. With no live parent and a full-goal mission, the chip becomes delivery owner and follows the repo profile. No second lifecycle approval.',
|
|
93
|
-
'Every repair is ROOT-CAUSE — fix the pattern that makes the friction class possible, never a guard on today\'s trigger.',
|
|
94
|
-
'A per-turn reminder fires ONLY when live friction is DETECTED (the stress signal — repetition / errors / churn), never on a timer — act on it, or say "clear" and continue.',
|
|
95
|
-
].join('\n');
|
|
78
|
+
const summary = bootstrapSummary(env);
|
|
79
|
+
return `⚠ BOOTSTRAP FRICTION detected${repeats}: setup/toolchain failure (${b.signal || 'setup'}) around \`${b.command || 'unknown command'}\`.${error} MEMORY FIRST: \`greprag memory search "${summary}"\`. Apply a clear overcome + continue. No answer, remembered overcome fails, or the same failure repeats → \`greprag fix spawn --type env "${summary}"\`.`;
|
|
96
80
|
}
|
|
97
81
|
/** Normalize any prior-stats blob (possibly missing/partial/corrupt) into a fully
|
|
98
82
|
* populated FrictionStats. Pure, total. */
|
|
@@ -136,13 +120,10 @@ function tallyMechanicSpawn(prior) {
|
|
|
136
120
|
return s;
|
|
137
121
|
}
|
|
138
122
|
// ---------- Registry module --------------------------------------------------
|
|
139
|
-
/** The active Mechanic
|
|
140
|
-
*
|
|
141
|
-
* 2026-06-22 as wallpaper). `dependsOn` the chip-spawn pointer so the chip concept
|
|
142
|
-
* it references is already loaded — the boot sequence places it AFTER chips. */
|
|
123
|
+
/** The active Mechanic reminder. The grepragOS primer owns the one startup schema;
|
|
124
|
+
* this module is reminder-only and fires only from live stress/bootstrap state. */
|
|
143
125
|
exports.mechanicFrictionModule = {
|
|
144
126
|
id: 'mechanic-friction',
|
|
145
|
-
dependsOn: ['chip-spawn-pointer'],
|
|
146
127
|
// Stress fires the real friction reminder. A designated Mechanic also creates a
|
|
147
128
|
// one-shot ambient route notice per source session/role epoch (hook-stamped).
|
|
148
129
|
detect: (env) => {
|
|
@@ -155,12 +136,7 @@ exports.mechanicFrictionModule = {
|
|
|
155
136
|
return { tier: 'ambient' };
|
|
156
137
|
return { tier: 'silent' };
|
|
157
138
|
},
|
|
158
|
-
announce: (
|
|
159
|
-
const base = buildMechanicAnnounce();
|
|
160
|
-
if (env.mechanicLive && !env.mechanic)
|
|
161
|
-
return `${base}\n\n${buildMechanicLiveNotice(env)}`;
|
|
162
|
-
return base;
|
|
163
|
-
},
|
|
139
|
+
announce: () => null,
|
|
164
140
|
reminder: (d, env) => {
|
|
165
141
|
if (d.detail?.kind === 'bootstrap-friction')
|
|
166
142
|
return buildBootstrapFrictionReminder(env);
|
package/dist/commands/init.js
CHANGED
|
@@ -1130,18 +1130,9 @@ function applyCodexHooks(config) {
|
|
|
1130
1130
|
if (removedPostToolInbox) {
|
|
1131
1131
|
changes.push(`Removed ${removedPostToolInbox} Codex PostToolUse inbox hook(s); UserPromptSubmit now owns inbox steering`);
|
|
1132
1132
|
}
|
|
1133
|
-
const
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
};
|
|
1137
|
-
if (!hasGrepragHook(config.hooks.PostToolUse, 'codex-posttooluse')) {
|
|
1138
|
-
if (!config.hooks.PostToolUse)
|
|
1139
|
-
config.hooks.PostToolUse = [];
|
|
1140
|
-
config.hooks.PostToolUse.push(checkpointHook);
|
|
1141
|
-
changes.push('Added Codex PostToolUse hook (successful checkpoint coordination)');
|
|
1142
|
-
}
|
|
1143
|
-
else {
|
|
1144
|
-
changes.push('Codex PostToolUse checkpoint hook already configured (skipped)');
|
|
1133
|
+
const removedPostToolCoord = removeGrepragHook(config.hooks, 'PostToolUse', 'codex-posttooluse');
|
|
1134
|
+
if (removedPostToolCoord) {
|
|
1135
|
+
changes.push(`Removed ${removedPostToolCoord} Codex commit-triggered coordination hook(s); delivery coordination now starts at PreToolUse`);
|
|
1145
1136
|
}
|
|
1146
1137
|
const permissionHook = {
|
|
1147
1138
|
matcher: '',
|
|
@@ -1199,8 +1190,8 @@ function applyCodexHooks(config) {
|
|
|
1199
1190
|
];
|
|
1200
1191
|
// adr: adr/codex-hook-latency.md
|
|
1201
1192
|
// Collapse the hot PreToolUse safety pair into one fast command process. It
|
|
1202
|
-
// runs the chip/apply_patch guard
|
|
1203
|
-
//
|
|
1193
|
+
// runs the chip/apply_patch guard, one-shot delivery coordination, and local
|
|
1194
|
+
// Bash safety checks. The legacy split coordination gate is removed.
|
|
1204
1195
|
const removedPreToolChip = removeGrepragHook(config.hooks, 'PreToolUse', 'codex-chip-hook');
|
|
1205
1196
|
const removedPreToolCoord = removeGrepragHook(config.hooks, 'PreToolUse', 'codex-coordinate-gate');
|
|
1206
1197
|
if (removedPreToolChip || removedPreToolCoord) {
|
|
@@ -1208,16 +1199,16 @@ function applyCodexHooks(config) {
|
|
|
1208
1199
|
}
|
|
1209
1200
|
const preToolUseHook = {
|
|
1210
1201
|
matcher: '',
|
|
1211
|
-
hooks: [commandHook('codex-pretooluse', 10, 'Checking GrepRAG Codex safety', 'greprag-codex-hook')],
|
|
1202
|
+
hooks: [commandHook('codex-pretooluse', 10, 'Checking GrepRAG Codex delivery and safety', 'greprag-codex-hook')],
|
|
1212
1203
|
};
|
|
1213
1204
|
if (!hasGrepragHookWithMatcher(config.hooks.PreToolUse, 'codex-pretooluse', preToolUseHook.matcher)) {
|
|
1214
1205
|
if (!config.hooks.PreToolUse)
|
|
1215
1206
|
config.hooks.PreToolUse = [];
|
|
1216
1207
|
config.hooks.PreToolUse.push(preToolUseHook);
|
|
1217
|
-
changes.push('Added Codex PreToolUse hook (
|
|
1208
|
+
changes.push('Added Codex PreToolUse hook (delivery coordination + local safety)');
|
|
1218
1209
|
}
|
|
1219
1210
|
else {
|
|
1220
|
-
changes.push('Codex PreToolUse
|
|
1211
|
+
changes.push('Codex PreToolUse delivery/safety hook already configured (skipped)');
|
|
1221
1212
|
}
|
|
1222
1213
|
const removedStopChip = removeGrepragHook(config.hooks, 'Stop', 'codex-chip-hook');
|
|
1223
1214
|
if (removedStopChip) {
|
|
@@ -1278,7 +1269,7 @@ function normalizeCodexHookCommands(hooks) {
|
|
|
1278
1269
|
'codex-permission-context': { timeout: 3, statusMessage: 'Loading GrepRAG approval context' },
|
|
1279
1270
|
'codex-subagent-start': { timeout: 3, statusMessage: 'Recording GrepRAG subagent metadata' },
|
|
1280
1271
|
'codex-chip-hook': { timeout: 3, statusMessage: 'Enforcing Codex chip contract', runner: 'greprag-codex-hook' },
|
|
1281
|
-
'codex-pretooluse': { timeout: 10, statusMessage: 'Checking GrepRAG Codex safety', runner: 'greprag-codex-hook' },
|
|
1272
|
+
'codex-pretooluse': { timeout: 10, statusMessage: 'Checking GrepRAG Codex delivery and safety', runner: 'greprag-codex-hook' },
|
|
1282
1273
|
'codex-coordinate-gate': { timeout: 10, statusMessage: 'Checking peer coordination' },
|
|
1283
1274
|
'codex-store': { timeout: 3, statusMessage: 'Storing GrepRAG turn', runner: 'greprag-codex-hook' },
|
|
1284
1275
|
};
|
package/dist/commands/load.js
CHANGED
|
@@ -63,7 +63,7 @@ const doc_mirror_client_1 = require("../doc-mirror-client");
|
|
|
63
63
|
const LIBRARY = {
|
|
64
64
|
os: {
|
|
65
65
|
files: ['skill/templates/os.md'],
|
|
66
|
-
purpose: 'grepragOS — the operating laws: doctrine vs state, discoverability, pull-before-derive, friction ⇒ fix spawn, teach the system not the chat.',
|
|
66
|
+
purpose: 'grepragOS — the operating laws: doctrine vs state, discoverability, pull-before-derive, memory-first friction recovery, unresolved friction ⇒ fix spawn, teach the system not the chat.',
|
|
67
67
|
},
|
|
68
68
|
'chip-spawn': {
|
|
69
69
|
files: ['skill/templates/chip-spawn.md'],
|
|
@@ -29,9 +29,9 @@ function buildOpenCodeEnv(params) {
|
|
|
29
29
|
// No opencode-native friction detector is wired yet: Claude Code's stress /
|
|
30
30
|
// bootstrap-friction signals come from state-trigger.ts reading the
|
|
31
31
|
// transcript in hook.ts, and opencode has no equivalent source plumbed.
|
|
32
|
-
// 0 = calm keeps the mechanic-friction
|
|
33
|
-
//
|
|
34
|
-
// parked with scored options in the chip report
|
|
32
|
+
// 0 = calm keeps the reminder-only mechanic-friction module silent. The
|
|
33
|
+
// os-primer still lands the memory-first escalation doctrine every session.
|
|
34
|
+
// Detector design is parked with scored options in the chip report
|
|
35
35
|
// (chip/opencode-fix-chip-system-parity).
|
|
36
36
|
stress: 0,
|
|
37
37
|
armed: params.armed ?? false,
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.osPrimerModule = void 0;
|
|
15
15
|
exports.buildOsPrimer = buildOsPrimer;
|
|
16
|
+
// adr: adr/friction-memory-first.md
|
|
16
17
|
function buildOsPrimer(env) {
|
|
17
18
|
// The chip-spawn method is per-harness (Codex never sees the Claude entry —
|
|
18
19
|
// the same wall the codex-chip-spawn pointer module enforces; opencode's
|
|
@@ -24,8 +25,8 @@ function buildOsPrimer(env) {
|
|
|
24
25
|
'[grepragOS — the operating laws. Full doctrine: `greprag load os`.]',
|
|
25
26
|
'• Doctrine vs state: methods ship in the CLI (`greprag load`); live state lives in the repo. A skill that depends on repo state carries a "STATE — read these first" block naming exact paths.',
|
|
26
27
|
'• Discoverability: every durable artifact must be findable next session — docs auto-register, skills auto-mirror, decisions get a dated ADR/decision-log entry, everything else gets its path named in the owning skill/doc. If nothing points at it, you didn\'t finish.',
|
|
27
|
-
'• Pull before derive: `greprag memory search` / `corpus search` / `load` BEFORE asking the operator or re-deriving what the project already knows.
|
|
28
|
-
`•
|
|
28
|
+
'• Pull before derive: `greprag memory search` / `corpus search` / `load` BEFORE asking the operator or re-deriving what the project already knows. Friction or an env/toolchain/worktree/secret bootstrap roadblock → search Memory for the exact error/friction + repo/tool before inventing a workaround. Clear overcome → apply it and continue. Named person/org/project/repo/customer/handle or unexplained proper noun → search Memory before guessing, unless fully defined in-turn.',
|
|
29
|
+
`• Unresolved friction ⇒ fix: only when Memory has no answer, the remembered overcome fails, or the same friction repeats. Then type by durable repair surface (\`harness|doctrine|injection|env|code\`) ⇒ \`greprag fix spawn --type <type> "<unit>"\`. It PRINTS a fix-chip mission — YOU then create the visible child task with that mission as its first message (how: \`greprag load ${spawnEntry}\`, or your harness's native task tool). Repo write = isolated worktree; data-only row/diagnosis = no repo write. One unresolved unit per chip; the chip fixes, verifies, checkpoints, and hands it to the mission delivery owner.`,
|
|
29
30
|
'• Teach the system, not the chat: explained twice by the operator ⇒ it belongs in a skill / load entry / STATE block / ADR, not the conversation.',
|
|
30
31
|
].join('\n');
|
|
31
32
|
}
|
|
@@ -30,10 +30,9 @@ const loadout_reminder_1 = require("./loadout-reminder");
|
|
|
30
30
|
const delivery_reminder_1 = require("./delivery-reminder");
|
|
31
31
|
/** Registry order = display order. THE single agent-facing announce/reminder assembly:
|
|
32
32
|
* the hook does I/O → fills ReminderEnv → collectAnnounces (SessionStart) / collectReminders
|
|
33
|
-
* (per turn) render every module here in this order.
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* SessionStart. Add a module here to wire it into BOTH surfaces at once. */
|
|
33
|
+
* (per turn) render every module here in this order. A module may own either surface or both;
|
|
34
|
+
* os-primer owns startup friction doctrine and mechanic-friction is reminder-only.
|
|
35
|
+
* Add a module here to wire it into the appropriate surfaces at once. */
|
|
37
36
|
exports.REGISTRY = [
|
|
38
37
|
os_primer_reminder_1.osPrimerModule, // grepragOS constitution — the frame every other primer hangs off; boots FIRST
|
|
39
38
|
inbox_primer_reminder_1.inboxPrimerModule,
|
package/dist/commands/status.js
CHANGED
|
@@ -327,8 +327,12 @@ function renderShims(w) {
|
|
|
327
327
|
const lines = ['', 'Windows shims (what each PowerShell/CMD shim on PATH actually executes):'];
|
|
328
328
|
for (const s of w.shims) {
|
|
329
329
|
const ver = s.version ? `v${s.version}` : '(version unknown)';
|
|
330
|
-
const
|
|
331
|
-
|
|
330
|
+
const details = [
|
|
331
|
+
...(s.incomplete ? [`missing ${s.missingFiles.join(', ')}`] : []),
|
|
332
|
+
...(s.volatileForward ? ['fnm temp forward'] : []),
|
|
333
|
+
];
|
|
334
|
+
const suffix = details.length ? ` ${details.join(', ')}` : '';
|
|
335
|
+
const flag = s.stale ? ` ⚠ STALE${suffix}` : s.incomplete ? ` ⚠ INCOMPLETE${suffix}` : s.volatileForward ? ` ⚠ VOLATILE${suffix}` : s.unresolvable ? ' ? unresolvable' : '';
|
|
332
336
|
lines.push(` ${s.name}${s.active ? '' : ' (shadowed)'}: ${s.shimPath} → ${ver}${flag}`);
|
|
333
337
|
}
|
|
334
338
|
if (w.mismatch) {
|
|
@@ -1752,8 +1752,8 @@ function buildOsPrimer(env) {
|
|
|
1752
1752
|
"[grepragOS \u2014 the operating laws. Full doctrine: `greprag load os`.]",
|
|
1753
1753
|
'\u2022 Doctrine vs state: methods ship in the CLI (`greprag load`); live state lives in the repo. A skill that depends on repo state carries a "STATE \u2014 read these first" block naming exact paths.',
|
|
1754
1754
|
"\u2022 Discoverability: every durable artifact must be findable next session \u2014 docs auto-register, skills auto-mirror, decisions get a dated ADR/decision-log entry, everything else gets its path named in the owning skill/doc. If nothing points at it, you didn't finish.",
|
|
1755
|
-
"\u2022 Pull before derive: `greprag memory search` / `corpus search` / `load` BEFORE asking the operator or re-deriving what the project already knows.
|
|
1756
|
-
`\u2022
|
|
1755
|
+
"\u2022 Pull before derive: `greprag memory search` / `corpus search` / `load` BEFORE asking the operator or re-deriving what the project already knows. Friction or an env/toolchain/worktree/secret bootstrap roadblock \u2192 search Memory for the exact error/friction + repo/tool before inventing a workaround. Clear overcome \u2192 apply it and continue. Named person/org/project/repo/customer/handle or unexplained proper noun \u2192 search Memory before guessing, unless fully defined in-turn.",
|
|
1756
|
+
`\u2022 Unresolved friction \u21D2 fix: only when Memory has no answer, the remembered overcome fails, or the same friction repeats. Then type by durable repair surface (\`harness|doctrine|injection|env|code\`) \u21D2 \`greprag fix spawn --type <type> "<unit>"\`. It PRINTS a fix-chip mission \u2014 YOU then create the visible child task with that mission as its first message (how: \`greprag load ${spawnEntry}\`, or your harness's native task tool). Repo write = isolated worktree; data-only row/diagnosis = no repo write. One unresolved unit per chip; the chip fixes, verifies, checkpoints, and hands it to the mission delivery owner.`,
|
|
1757
1757
|
"\u2022 Teach the system, not the chat: explained twice by the operator \u21D2 it belongs in a skill / load entry / STATE block / ADR, not the conversation."
|
|
1758
1758
|
].join("\n");
|
|
1759
1759
|
}
|
|
@@ -2022,15 +2022,16 @@ function frictionReminderTier(stress) {
|
|
|
2022
2022
|
return "silent";
|
|
2023
2023
|
}
|
|
2024
2024
|
var FIX_SPAWN = 'greprag fix spawn --type <harness|doctrine|injection|env|code> "<one unit>"';
|
|
2025
|
+
var MEMORY_SEARCH = 'greprag memory search "<exact error/friction + repo/tool>"';
|
|
2025
2026
|
function buildMechanicLiveNotice(_env) {
|
|
2026
|
-
return `GrepRAG Mechanic is live. Friction
|
|
2027
|
+
return `GrepRAG Mechanic is live. Friction order: \`${MEMORY_SEARCH}\` first; apply a clear overcome and continue. No answer, failed overcome, or repeated friction \u2192 \`${FIX_SPAWN}\`. Check role only when needed: \`greprag mechanic role\`.`;
|
|
2027
2028
|
}
|
|
2028
2029
|
function buildFrictionReminder(tier, env) {
|
|
2029
2030
|
switch (tier) {
|
|
2030
2031
|
case "nag":
|
|
2031
|
-
return `\u26A0 FRICTION HIGH (live signal: repetition / errors / churn) \u2014 STOP pushing through.
|
|
2032
|
+
return `\u26A0 FRICTION HIGH (live signal: repetition / errors / churn) \u2014 STOP pushing through. MEMORY FIRST: \`${MEMORY_SEARCH}\`. Clear overcome \u2192 apply + continue. No answer, remembered overcome fails, or same friction repeats \u2192 \`${FIX_SPAWN}\`. No friction? say "clear" + continue.`;
|
|
2032
2033
|
case "nudge":
|
|
2033
|
-
return `\u26A0 Friction detected (live signal) \u2014 repeated yourself, fought a tool, or hit a wall?
|
|
2034
|
+
return `\u26A0 Friction detected (live signal) \u2014 repeated yourself, fought a tool, or hit a wall? Search first: \`${MEMORY_SEARCH}\`. Apply a clear overcome + continue; no answer, failed overcome, or repeat \u2192 \`${FIX_SPAWN}\`. Otherwise say "clear" + continue.`;
|
|
2034
2035
|
default:
|
|
2035
2036
|
return null;
|
|
2036
2037
|
}
|
|
@@ -2041,22 +2042,11 @@ function buildBootstrapFrictionReminder(env) {
|
|
|
2041
2042
|
return null;
|
|
2042
2043
|
const repeats = b.repeats && b.repeats > 1 ? ` repeated ${b.repeats}x` : "";
|
|
2043
2044
|
const error = b.error ? ` Last error: ${b.error}` : "";
|
|
2044
|
-
|
|
2045
|
-
}
|
|
2046
|
-
function buildMechanicAnnounce() {
|
|
2047
|
-
return [
|
|
2048
|
-
"[Fix reflex \u2014 friction is fixed at the MOMENT it happens, one chip per unit (grepragOS law; full doctrine: `greprag load os`).]",
|
|
2049
|
-
"WHEN friction happens \u2014 repeated yourself, fought a tool, got corrected >=2x on the same thing, rediscovered something already known, or hit a setup/toolchain failure not caused by code you just edited \u2014 spawn its fix chip NOW, then keep working. Ordinary misunderstanding or misreading user intent is not friction unless it repeats or exposes a durable doctrine/harness/injection/env/code failure. One unit = one chip; adjacent friction gets its own spawn. Never queue friction for later.",
|
|
2050
|
-
'TYPE = durable repair surface: harness=hooks/watchers/task dispatch; doctrine=greprag load/skills/AGENTS; injection=recap/Capture/doc-pointer/stateful injection; env=bootstrap/deps/scripts/worktree setup; code=product/source behavior. Use: `greprag fix spawn --type <type> "<one unit>"`.',
|
|
2051
|
-
"WORKSPACE ROUTING: `fix spawn` detects usable Git history before dispatch \u2014 Git uses an isolated worktree; non-Git, unavailable Git, or no commit uses the project-local task with serialized writes. No fail-then-fallback attempt.",
|
|
2052
|
-
"THE CHIP'S CONTRACT (it self-enforces; you just spawn): identify the exact friction \u2192 make the smallest durable root-cause fix \u2192 explain and verify it \u2192 checkpoint \u2192 hand it to the mission delivery owner. With no live parent and a full-goal mission, the chip becomes delivery owner and follows the repo profile. No second lifecycle approval.",
|
|
2053
|
-
"Every repair is ROOT-CAUSE \u2014 fix the pattern that makes the friction class possible, never a guard on today's trigger.",
|
|
2054
|
-
'A per-turn reminder fires ONLY when live friction is DETECTED (the stress signal \u2014 repetition / errors / churn), never on a timer \u2014 act on it, or say "clear" and continue.'
|
|
2055
|
-
].join("\n");
|
|
2045
|
+
const summary = bootstrapSummary(env);
|
|
2046
|
+
return `\u26A0 BOOTSTRAP FRICTION detected${repeats}: setup/toolchain failure (${b.signal || "setup"}) around \`${b.command || "unknown command"}\`.${error} MEMORY FIRST: \`greprag memory search "${summary}"\`. Apply a clear overcome + continue. No answer, remembered overcome fails, or the same failure repeats \u2192 \`greprag fix spawn --type env "${summary}"\`.`;
|
|
2056
2047
|
}
|
|
2057
2048
|
var mechanicFrictionModule = {
|
|
2058
2049
|
id: "mechanic-friction",
|
|
2059
|
-
dependsOn: ["chip-spawn-pointer"],
|
|
2060
2050
|
// Stress fires the real friction reminder. A designated Mechanic also creates a
|
|
2061
2051
|
// one-shot ambient route notice per source session/role epoch (hook-stamped).
|
|
2062
2052
|
detect: (env) => {
|
|
@@ -2069,14 +2059,7 @@ var mechanicFrictionModule = {
|
|
|
2069
2059
|
return { tier: "ambient" };
|
|
2070
2060
|
return { tier: "silent" };
|
|
2071
2061
|
},
|
|
2072
|
-
announce: (
|
|
2073
|
-
const base = buildMechanicAnnounce();
|
|
2074
|
-
if (env.mechanicLive && !env.mechanic)
|
|
2075
|
-
return `${base}
|
|
2076
|
-
|
|
2077
|
-
${buildMechanicLiveNotice(env)}`;
|
|
2078
|
-
return base;
|
|
2079
|
-
},
|
|
2062
|
+
announce: () => null,
|
|
2080
2063
|
reminder: (d, env) => {
|
|
2081
2064
|
if (d.detail?.kind === "bootstrap-friction")
|
|
2082
2065
|
return buildBootstrapFrictionReminder(env);
|
|
@@ -2418,27 +2401,26 @@ var loadoutRegistrarModule = {
|
|
|
2418
2401
|
// src/commands/delivery-reminder.ts
|
|
2419
2402
|
function coordinationLine(platform) {
|
|
2420
2403
|
if (platform === "codex") {
|
|
2421
|
-
return "Codex
|
|
2404
|
+
return "Codex: at the merge/deploy gate, call codex_app.list_threads unfiltered, filter to peers in the same repo, then use codex_app.send_message_to_thread once for ready or overlapping work.";
|
|
2422
2405
|
}
|
|
2423
2406
|
if (platform === "claude-code") {
|
|
2424
|
-
return "Claude Code
|
|
2407
|
+
return "Claude Code: at the merge/deploy gate, discover peers in the same repo and send one concise delivery notice to peers with ready or overlapping work.";
|
|
2425
2408
|
}
|
|
2426
2409
|
if (platform === "opencode") {
|
|
2427
|
-
return "OpenCode
|
|
2410
|
+
return "OpenCode: at the merge/deploy gate, use the GrepRAG project/session registry and send one concise delivery notice to peers with ready or overlapping work.";
|
|
2428
2411
|
}
|
|
2429
|
-
return "
|
|
2412
|
+
return "At the merge/deploy gate, coordinate once with peers in the same repo who have ready or overlapping work.";
|
|
2430
2413
|
}
|
|
2431
2414
|
function buildDeliveryAnnounce(env) {
|
|
2432
2415
|
if (!env.deliveryRepo)
|
|
2433
2416
|
return null;
|
|
2434
2417
|
const project = env.projectName?.trim() || "current repo";
|
|
2435
2418
|
return [
|
|
2436
|
-
`[DELIVERY
|
|
2437
|
-
"
|
|
2419
|
+
`[DELIVERY \u2014 ${project}]`,
|
|
2420
|
+
"Commit useful, passing work.",
|
|
2438
2421
|
coordinationLine(env.platform),
|
|
2439
|
-
"
|
|
2440
|
-
"Use
|
|
2441
|
-
"A full-goal delivery mission already provisions ordinary repo-scoped commit, merge, push, deploy, and verification; do not wait for a second keyword."
|
|
2422
|
+
"Include ready peer commits and clear known blockers, then merge to the default branch and deploy immediately. Missing replies and unfinished peer work do not block delivery.",
|
|
2423
|
+
"Use this repo's delivery profile and verify production."
|
|
2442
2424
|
].join("\n");
|
|
2443
2425
|
}
|
|
2444
2426
|
var deliveryControlModule = {
|
|
@@ -2558,9 +2540,9 @@ function buildOpenCodeEnv(params) {
|
|
|
2558
2540
|
// No opencode-native friction detector is wired yet: Claude Code's stress /
|
|
2559
2541
|
// bootstrap-friction signals come from state-trigger.ts reading the
|
|
2560
2542
|
// transcript in hook.ts, and opencode has no equivalent source plumbed.
|
|
2561
|
-
// 0 = calm keeps the mechanic-friction
|
|
2562
|
-
//
|
|
2563
|
-
// parked with scored options in the chip report
|
|
2543
|
+
// 0 = calm keeps the reminder-only mechanic-friction module silent. The
|
|
2544
|
+
// os-primer still lands the memory-first escalation doctrine every session.
|
|
2545
|
+
// Detector design is parked with scored options in the chip report
|
|
2564
2546
|
// (chip/opencode-fix-chip-system-parity).
|
|
2565
2547
|
stress: 0,
|
|
2566
2548
|
armed: params.armed ?? false,
|
package/dist/windows-shims.js
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* silently emitted no loadout announce and the report looked like a server
|
|
11
11
|
* bug. The CLI must catch that split itself: `greprag status` names every
|
|
12
12
|
* shim + the version it actually executes; `greprag doctor` rewrites stale
|
|
13
|
-
* shims to forward to a current one.
|
|
13
|
+
* shims to forward to a current one. Durable shims must not forward into
|
|
14
|
+
* fnm_multishells: those are per-shell temp directories and can disappear
|
|
15
|
+
* while the stable PATH entry remains.
|
|
14
16
|
*
|
|
15
17
|
* Pure helpers (parse/version/plan) are exported for unit tests; only
|
|
16
18
|
* `auditWindowsShims`/`repairStaleShims` touch PATH + disk.
|
|
@@ -55,6 +57,7 @@ exports.missingRequiredShimFiles = missingRequiredShimFiles;
|
|
|
55
57
|
exports.auditWindowsShims = auditWindowsShims;
|
|
56
58
|
exports.shimNeedsRepair = shimNeedsRepair;
|
|
57
59
|
exports.planShimRepairs = planShimRepairs;
|
|
60
|
+
exports.shimInstallCommand = shimInstallCommand;
|
|
58
61
|
exports.repairStaleShims = repairStaleShims;
|
|
59
62
|
const fs = __importStar(require("fs"));
|
|
60
63
|
const path = __importStar(require("path"));
|
|
@@ -111,6 +114,9 @@ function missingRequiredShimFiles(scriptPath, name) {
|
|
|
111
114
|
return [];
|
|
112
115
|
return requiredFilesForShim(name).filter(file => !fs.existsSync(path.join(info.root, file)));
|
|
113
116
|
}
|
|
117
|
+
function isFnmMultishellPath(p) {
|
|
118
|
+
return !!p && /[\\/]fnm_multishells[\\/]/i.test(path.normalize(p));
|
|
119
|
+
}
|
|
114
120
|
/** Resolve one shim: follow up to 3 .cmd forwarding hops, then read the
|
|
115
121
|
* owning package version. */
|
|
116
122
|
function resolveShim(name, shimPath, active, installed) {
|
|
@@ -134,6 +140,7 @@ function resolveShim(name, shimPath, active, installed) {
|
|
|
134
140
|
}
|
|
135
141
|
const info = target ? shimPackageInfo(target) : null;
|
|
136
142
|
const missingFiles = info ? requiredFilesForShim(name).filter(file => !fs.existsSync(path.join(info.root, file))) : [];
|
|
143
|
+
const volatileForward = !isFnmMultishellPath(shimPath) && isFnmMultishellPath(target);
|
|
137
144
|
return {
|
|
138
145
|
name,
|
|
139
146
|
extension: path.extname(shimPath).toLowerCase() === '.ps1' ? '.ps1' : '.cmd',
|
|
@@ -143,6 +150,7 @@ function resolveShim(name, shimPath, active, installed) {
|
|
|
143
150
|
version: info?.version ?? null,
|
|
144
151
|
stale: info !== null && info.version !== installed,
|
|
145
152
|
incomplete: missingFiles.length > 0,
|
|
153
|
+
volatileForward,
|
|
146
154
|
missingFiles,
|
|
147
155
|
unresolvable: target === null || info === null,
|
|
148
156
|
};
|
|
@@ -199,7 +207,7 @@ function auditWindowsShims(installedVersion) {
|
|
|
199
207
|
return out;
|
|
200
208
|
}
|
|
201
209
|
function shimNeedsRepair(shim) {
|
|
202
|
-
return shim.stale || shim.incomplete;
|
|
210
|
+
return shim.stale || shim.incomplete || shim.volatileForward;
|
|
203
211
|
}
|
|
204
212
|
function canForward(from, to) {
|
|
205
213
|
if (from.extension === '.ps1')
|
|
@@ -214,6 +222,7 @@ function planShimRepairs(audits) {
|
|
|
214
222
|
&& !shimNeedsRepair(a)
|
|
215
223
|
&& !a.unresolvable
|
|
216
224
|
&& a.shimPath !== shim.shimPath
|
|
225
|
+
&& (isFnmMultishellPath(shim.shimPath) || !isFnmMultishellPath(a.shimPath))
|
|
217
226
|
&& canForward(shim, a));
|
|
218
227
|
return { shim, forwardTo: healthy ? healthy.shimPath : null };
|
|
219
228
|
});
|
|
@@ -224,8 +233,15 @@ function describeProblem(shim) {
|
|
|
224
233
|
parts.push(`v${shim.version}`);
|
|
225
234
|
if (shim.incomplete)
|
|
226
235
|
parts.push(`missing ${shim.missingFiles.join(', ')}`);
|
|
236
|
+
if (shim.volatileForward)
|
|
237
|
+
parts.push('fnm temp forward');
|
|
227
238
|
return parts.length ? parts.join(', ') : 'unhealthy';
|
|
228
239
|
}
|
|
240
|
+
function shimInstallCommand() {
|
|
241
|
+
return process.platform === 'win32'
|
|
242
|
+
? 'npm i -g greprag@latest --prefix "%APPDATA%\\npm"'
|
|
243
|
+
: 'npm i -g greprag@latest';
|
|
244
|
+
}
|
|
229
245
|
function writeForwarder(shim, forwardTo) {
|
|
230
246
|
if (shim.extension === '.ps1') {
|
|
231
247
|
fs.writeFileSync(shim.shimPath, [
|
|
@@ -250,7 +266,7 @@ function repairStaleShims(repairs) {
|
|
|
250
266
|
const lines = [];
|
|
251
267
|
for (const r of repairs) {
|
|
252
268
|
if (!r.forwardTo) {
|
|
253
|
-
lines.push(`✗ ${r.shim.shimPath} is unhealthy (${describeProblem(r.shim)}) and no current ${r.shim.name} shim exists on PATH to forward to — run
|
|
269
|
+
lines.push(`✗ ${r.shim.shimPath} is unhealthy (${describeProblem(r.shim)}) and no durable current ${r.shim.name} shim exists on PATH to forward to — run \`${shimInstallCommand()}\`, then re-run doctor.`);
|
|
254
270
|
continue;
|
|
255
271
|
}
|
|
256
272
|
try {
|
package/package.json
CHANGED
package/skill/mechanic/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: mechanic
|
|
3
3
|
description: |
|
|
4
4
|
The Mechanic — keep the harness healthy. The loop is friction → fix → repair:
|
|
5
|
-
spawn one fix chip per
|
|
5
|
+
recall known overcomes first; spawn one fix chip per unresolved friction unit.
|
|
6
6
|
Digest the existing fix queue and audit/design notes, audit fixes for drift,
|
|
7
7
|
mine episodic memory, promote project-agnostic repairs to global. One-at-a-time
|
|
8
8
|
conversational review — never bulk.
|
|
@@ -19,10 +19,13 @@ license: MIT
|
|
|
19
19
|
|
|
20
20
|
# Mechanic
|
|
21
21
|
|
|
22
|
-
> **
|
|
23
|
-
> routing/queueing reflex below wherever they conflict).**
|
|
24
|
-
>
|
|
25
|
-
>
|
|
22
|
+
> **Memory-first friction reflex (refined 2026-07-28, grepragOS laws 3–4 —
|
|
23
|
+
> supersedes the routing/queueing reflex below wherever they conflict).** Search
|
|
24
|
+
> Memory for the exact error/friction plus repo/tool first. Apply a clear
|
|
25
|
+
> remembered overcome and continue. Only when Memory has no answer, the overcome
|
|
26
|
+
> fails, or the same friction repeats does
|
|
27
|
+
> `greprag fix spawn --type <type> "<one unit>"` emit a FIX chip for that
|
|
28
|
+
> unresolved unit. The chip's
|
|
26
29
|
> contract: identify the exact friction → make the smallest durable root-cause
|
|
27
30
|
> fix → explain the friction and fix in human terms → checkpoint → hand it to
|
|
28
31
|
> the mission delivery owner. A full-goal chip with no live parent becomes that
|
|
@@ -46,7 +49,7 @@ task.
|
|
|
46
49
|
|
|
47
50
|
The Mechanic keeps the harness healthy. The loop is **friction → fix → repair**:
|
|
48
51
|
|
|
49
|
-
- **friction** — a rough spot the agent or operator hit: a gotcha, a rediscovery, a repeated correction, churn, rework. Ordinary misunderstanding or misreading user intent is not friction unless it repeats or exposes a durable doctrine/harness/injection/env/code failure.
|
|
52
|
+
- **friction** — a rough spot the agent or operator hit: a gotcha, a rediscovery, a repeated correction, churn, rework. Ordinary misunderstanding or misreading user intent is not friction unless it repeats or exposes a durable doctrine/harness/injection/env/code failure. Search Memory first; a clear overcome is applied inline, while unresolved friction becomes one `greprag fix spawn --type <type> "<unit>"` chip.
|
|
50
53
|
- **fix** — the repair mission or audit note. Live fixes are owned by spawned FIX chips; the queue remains for existing backlog and deliberate design-input notes.
|
|
51
54
|
- **repair** — the fix wired in so it can't recur: a hook, a code change, or *surfacing it* (a doc line / a fact-seed / an injection). A repaired fix is done.
|
|
52
55
|
|
|
@@ -292,11 +295,11 @@ greprag mechanic off / on PANIC SWITCH — local file, no network; s
|
|
|
292
295
|
|
|
293
296
|
**Legacy Mechanic role.** A `--mechanic` inbox watcher can still receive
|
|
294
297
|
legacy `mechanic_friction` / `mechanic_reply` rows in addition to its own session
|
|
295
|
-
lane, but live working sessions no longer route friction there.
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
that teaches this
|
|
299
|
-
threads. The Mechanic replies with `greprag mechanic reply --to <source-session>
|
|
298
|
+
lane, but live working sessions no longer route friction there. Laws 3–4 search
|
|
299
|
+
Memory first, then route each unresolved unit through
|
|
300
|
+
`greprag fix spawn --type <type> "<one unit>"`. `greprag mechanic set` only
|
|
301
|
+
fans out a notice that teaches this reflex and preserves the reply rail for old
|
|
302
|
+
mechanic threads. The Mechanic replies with `greprag mechanic reply --to <source-session>
|
|
300
303
|
"..."` only for legacy follow-up coordination.
|
|
301
304
|
|
|
302
305
|
**Born shadow → graduate.** Every repair is born `shadow`: it matches and logs would-have-fired
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
The skill-tuning reflex injects a directive when a skill under break-in loads. The old directive said "watch your execution, emit a SELF-TUNING block, propose a fix" — vague (it pointed at the *run*, not the *skill*) and propose-only (no edit ever landed). This doc replaces that with **six locatable skill defects**, each mapped to one concrete edit. The reflex's loop is: **detect a defined friction → locate it in the skill's SKILL.md → apply the fix per the matching recipe → show the diff for one accept/revert.**
|
|
8
8
|
|
|
9
|
-
A friction that doesn't match one of the six below is **not auto-fixable inline
|
|
9
|
+
A friction that doesn't match one of the six below is **not auto-fixable inline**. Search Memory for the exact skill/friction first; apply a clear overcome and continue. If Memory has no answer, the overcome fails, or the friction repeats, spawn one doctrine fix chip (`greprag fix spawn --type doctrine "<skill friction unit>"`). Use `greprag fix log` only for deliberate audit/design notes. Do not invent edits outside these recipes.
|
|
10
10
|
|
|
11
11
|
## The two edit primitives (skill-optimize vocabulary)
|
|
12
12
|
|
package/skill/templates/os.md
CHANGED
|
@@ -52,11 +52,14 @@ project, repo, customer, handle, or unexplained proper noun, search memory for
|
|
|
52
52
|
that entity before responding when prior context could affect the answer. Do
|
|
53
53
|
not do this for generic nouns or for entities fully defined in the current turn.
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
For any env, toolchain, worktree, secret, tool, hook, command, correction, or
|
|
56
|
+
repetition friction, search Memory for the exact error/friction plus repo/tool.
|
|
57
|
+
If Memory has a clear overcome, apply it and continue.
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
## Law 4 — Unresolved friction ⇒ fix spawn
|
|
60
|
+
|
|
61
|
+
Escalate only when Memory has no answer, the remembered overcome fails, or the
|
|
62
|
+
same friction repeats:
|
|
60
63
|
|
|
61
64
|
greprag fix spawn --type <harness|doctrine|injection|env|code> "<one unit>"
|
|
62
65
|
|
|
@@ -66,9 +69,9 @@ wasn't your edit — is fixed at the MOMENT it happens:
|
|
|
66
69
|
(Claude Code `spawn_task`, Codex `codex_app__create_thread`, opencode
|
|
67
70
|
bootloader) and send the mission as its first message. Method:
|
|
68
71
|
`greprag load chip-spawn`.
|
|
69
|
-
- **One unit = one chip.** A fix chip exists for exactly one unit of
|
|
70
|
-
friction. Adjacent friction gets its own spawn — a chip that
|
|
71
|
-
friction loses the context each unit needs.
|
|
72
|
+
- **One unresolved unit = one chip.** A fix chip exists for exactly one unit of
|
|
73
|
+
unresolved friction. Adjacent friction gets its own spawn — a chip that
|
|
74
|
+
absorbs new friction loses the context each unit needs.
|
|
72
75
|
- **Type = durable repair surface.** `harness` fixes hooks/watchers/task
|
|
73
76
|
dispatch/harness behavior; `doctrine` fixes `greprag load`, bundled skills,
|
|
74
77
|
and rendered AGENTS/CLAUDE instructions; `injection` fixes recap, Capture,
|
|
@@ -86,10 +89,10 @@ wasn't your edit — is fixed at the MOMENT it happens:
|
|
|
86
89
|
the operator before touching code. Rarely can a chip design the durable
|
|
87
90
|
repair alone; the gate is what keeps repairs durable instead of
|
|
88
91
|
workarounds.
|
|
89
|
-
- **Never queue friction for later.** The queue-first reflex
|
|
90
|
-
periodic digestion) is retired; the
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
- **Never queue unresolved friction for later.** The queue-first reflex
|
|
93
|
+
(`fix log` → periodic digestion) is retired; the live context belongs with
|
|
94
|
+
the fix chip. `greprag fix log` survives only for audit trails and
|
|
95
|
+
design-input notes that are deliberately not chips.
|
|
93
96
|
- Every repair is ROOT-CAUSE: fix the pattern that makes the friction class
|
|
94
97
|
possible, never a guard on today's trigger.
|
|
95
98
|
|
|
@@ -98,4 +101,4 @@ wasn't your edit — is fixed at the MOMENT it happens:
|
|
|
98
101
|
If the operator explains the same thing twice, the explanation belongs in a
|
|
99
102
|
durable surface — a skill, a load entry, a STATE block, an ADR — not in the
|
|
100
103
|
conversation. Hand-taught doctrine that stays in chat dies with the session;
|
|
101
|
-
that is itself friction (Law 4 applies).
|
|
104
|
+
that is itself unresolved friction (Law 4 applies).
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/** Codex checkpoint coordination — detect a successful Git commit transition
|
|
3
|
-
* and inject the delivery handoff at that exact lifecycle boundary.
|
|
4
|
-
*
|
|
5
|
-
* PreToolUse records the prior HEAD around shell-capable tools. PostToolUse
|
|
6
|
-
* proves HEAD changed via a commit reflog action and emits immediately; the
|
|
7
|
-
* next PreToolUse is a fallback if that lifecycle event was unavailable.
|
|
8
|
-
* User prompt and executor-wrapper wording are irrelevant.
|
|
9
|
-
* adr: adr/codex-checkpoint-coordination.md */
|
|
10
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
-
}
|
|
16
|
-
Object.defineProperty(o, k2, desc);
|
|
17
|
-
}) : (function(o, m, k, k2) {
|
|
18
|
-
if (k2 === undefined) k2 = k;
|
|
19
|
-
o[k2] = m[k];
|
|
20
|
-
}));
|
|
21
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
-
}) : function(o, v) {
|
|
24
|
-
o["default"] = v;
|
|
25
|
-
});
|
|
26
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
-
var ownKeys = function(o) {
|
|
28
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
-
var ar = [];
|
|
30
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
-
return ar;
|
|
32
|
-
};
|
|
33
|
-
return ownKeys(o);
|
|
34
|
-
};
|
|
35
|
-
return function (mod) {
|
|
36
|
-
if (mod && mod.__esModule) return mod;
|
|
37
|
-
var result = {};
|
|
38
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
-
__setModuleDefault(result, mod);
|
|
40
|
-
return result;
|
|
41
|
-
};
|
|
42
|
-
})();
|
|
43
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
-
exports.checkpointCoordinationText = checkpointCoordinationText;
|
|
45
|
-
exports.recordCodexGitBoundary = recordCodexGitBoundary;
|
|
46
|
-
exports.evaluateCodexCommitResult = evaluateCodexCommitResult;
|
|
47
|
-
exports.evaluatePendingCodexCheckpoint = evaluatePendingCodexCheckpoint;
|
|
48
|
-
const crypto = __importStar(require("crypto"));
|
|
49
|
-
const fs = __importStar(require("fs"));
|
|
50
|
-
const path = __importStar(require("path"));
|
|
51
|
-
const child_process_1 = require("child_process");
|
|
52
|
-
function homeDir() {
|
|
53
|
-
return process.env.USERPROFILE || process.env.HOME || '';
|
|
54
|
-
}
|
|
55
|
-
function normalizedCwd(input) {
|
|
56
|
-
return path.resolve(input.cwd || process.cwd()).replace(/\\/g, '/').toLowerCase();
|
|
57
|
-
}
|
|
58
|
-
function statePath(input) {
|
|
59
|
-
const home = homeDir();
|
|
60
|
-
const session = (input.session_id || '').trim();
|
|
61
|
-
if (!home || !session)
|
|
62
|
-
return null;
|
|
63
|
-
const key = crypto.createHash('sha256')
|
|
64
|
-
.update(`${session}\0${normalizedCwd(input)}`)
|
|
65
|
-
.digest('hex')
|
|
66
|
-
.slice(0, 24);
|
|
67
|
-
return path.join(home, '.greprag', 'state', `codex-checkpoint-${key}.json`);
|
|
68
|
-
}
|
|
69
|
-
function git(cwd, args) {
|
|
70
|
-
try {
|
|
71
|
-
return (0, child_process_1.execFileSync)('git', args, {
|
|
72
|
-
cwd,
|
|
73
|
-
encoding: 'utf8',
|
|
74
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
75
|
-
windowsHide: true,
|
|
76
|
-
}).trim() || null;
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function currentHead(cwd) {
|
|
83
|
-
return git(cwd, ['rev-parse', 'HEAD']);
|
|
84
|
-
}
|
|
85
|
-
function currentBranch(cwd) {
|
|
86
|
-
return git(cwd, ['branch', '--show-current']) || '(detached HEAD)';
|
|
87
|
-
}
|
|
88
|
-
function latestHeadAction(cwd) {
|
|
89
|
-
return git(cwd, ['reflog', '-1', '--format=%gs', 'HEAD']) || '';
|
|
90
|
-
}
|
|
91
|
-
function isCommitHeadAction(cwd) {
|
|
92
|
-
return /^commit(?: \([^)]+\))?:/i.test(latestHeadAction(cwd));
|
|
93
|
-
}
|
|
94
|
-
function isShellCapableTool(input) {
|
|
95
|
-
const name = (input.tool_name || '').toLowerCase();
|
|
96
|
-
return /(?:^|[.:/_-])(bash|shell|exec|exec_command|write_stdin)$/.test(name);
|
|
97
|
-
}
|
|
98
|
-
function writePending(file, pending) {
|
|
99
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
100
|
-
fs.writeFileSync(file, JSON.stringify(pending, null, 2) + '\n');
|
|
101
|
-
}
|
|
102
|
-
function readPending(file) {
|
|
103
|
-
try {
|
|
104
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function clearPending(file) {
|
|
111
|
-
try {
|
|
112
|
-
fs.unlinkSync(file);
|
|
113
|
-
}
|
|
114
|
-
catch { /* already absent */ }
|
|
115
|
-
}
|
|
116
|
-
function consumePending(input, hookEventName) {
|
|
117
|
-
const file = statePath(input);
|
|
118
|
-
if (!file)
|
|
119
|
-
return null;
|
|
120
|
-
const pending = readPending(file);
|
|
121
|
-
if (!pending || pending.cwd !== normalizedCwd(input))
|
|
122
|
-
return null;
|
|
123
|
-
clearPending(file);
|
|
124
|
-
const cwd = input.cwd || process.cwd();
|
|
125
|
-
const afterHead = currentHead(cwd);
|
|
126
|
-
if (!afterHead || afterHead === pending.beforeHead || !isCommitHeadAction(cwd))
|
|
127
|
-
return null;
|
|
128
|
-
return {
|
|
129
|
-
hookSpecificOutput: {
|
|
130
|
-
hookEventName,
|
|
131
|
-
additionalContext: checkpointCoordinationText(currentBranch(cwd), afterHead.slice(0, 12)),
|
|
132
|
-
},
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
function checkpointCoordinationText(branch, sha) {
|
|
136
|
-
return `[DELIVERY COORDINATION — CHECKPOINT CREATED]
|
|
137
|
-
|
|
138
|
-
Checkpoint: ${branch} @ ${sha}
|
|
139
|
-
|
|
140
|
-
Coordinate now, before merge, push, deploy, publish, or release.
|
|
141
|
-
|
|
142
|
-
- Child task: send the LEAD your branch, SHA, checks, status, owned dirt, and blockers. The LEAD owns integration.
|
|
143
|
-
- LEAD/standalone task: call codex_app.list_threads unfiltered, scope to this repo/worktree, exclude yourself, and ask every live peer for its latest checkpoint, owned dirt, blockers, and sequencing needs.
|
|
144
|
-
- Elect exactly one delivery owner. If no peers exist, you are the owner.
|
|
145
|
-
- Use greprag send for cross-harness peers.
|
|
146
|
-
|
|
147
|
-
Do not begin a delivery action until coordination is settled.`;
|
|
148
|
-
}
|
|
149
|
-
/** PreToolUse leg: remember HEAD around any shell-capable action. The result
|
|
150
|
-
* leg classifies the actual Git transition, so nested executor syntax and
|
|
151
|
-
* dynamically composed commands do not matter. */
|
|
152
|
-
function recordCodexGitBoundary(input) {
|
|
153
|
-
if (input.hook_event_name !== 'PreToolUse')
|
|
154
|
-
return;
|
|
155
|
-
if (!isShellCapableTool(input))
|
|
156
|
-
return;
|
|
157
|
-
const file = statePath(input);
|
|
158
|
-
if (!file)
|
|
159
|
-
return;
|
|
160
|
-
const cwd = input.cwd || process.cwd();
|
|
161
|
-
writePending(file, {
|
|
162
|
-
sessionId: input.session_id || '',
|
|
163
|
-
cwd: normalizedCwd(input),
|
|
164
|
-
toolName: input.tool_name || '',
|
|
165
|
-
beforeHead: currentHead(cwd),
|
|
166
|
-
recordedAt: new Date().toISOString(),
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
/** Preferred result leg: emit on the successful commit's own PostToolUse. */
|
|
170
|
-
function evaluateCodexCommitResult(input) {
|
|
171
|
-
if (input.hook_event_name !== 'PostToolUse')
|
|
172
|
-
return null;
|
|
173
|
-
if (!isShellCapableTool(input))
|
|
174
|
-
return null;
|
|
175
|
-
return consumePending(input, 'PostToolUse');
|
|
176
|
-
}
|
|
177
|
-
/** Fallback result leg: if PostToolUse was unavailable, emit before the first
|
|
178
|
-
* later tool call. Failed/empty commits leave HEAD unchanged and clear
|
|
179
|
-
* silently. */
|
|
180
|
-
function evaluatePendingCodexCheckpoint(input) {
|
|
181
|
-
if (input.hook_event_name !== 'PreToolUse')
|
|
182
|
-
return null;
|
|
183
|
-
return consumePending(input, 'PreToolUse');
|
|
184
|
-
}
|