greprag 5.49.16 → 5.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/doc-pointer-reminder.d.ts +18 -0
- package/dist/commands/doc-pointer-reminder.js +41 -0
- package/dist/commands/doc-pointer-reminder.js.map +1 -0
- package/dist/commands/enrichment-health-reminder.d.ts +23 -0
- package/dist/commands/enrichment-health-reminder.js +39 -0
- package/dist/commands/enrichment-health-reminder.js.map +1 -0
- package/dist/commands/init.js +20 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/load.js +8 -0
- package/dist/commands/load.js.map +1 -1
- package/dist/commands/memory.js +40 -12
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/opencode-interrupt.d.ts +5 -0
- package/dist/commands/opencode-interrupt.js +8 -2
- package/dist/commands/opencode-interrupt.js.map +1 -1
- package/dist/commands/opencode-relay.js +1 -1
- package/dist/commands/opencode-relay.js.map +1 -1
- package/dist/commands/procedure.d.ts +15 -0
- package/dist/commands/procedure.js +167 -0
- package/dist/commands/procedure.js.map +1 -0
- package/dist/commands/reminder-registry.js +6 -0
- package/dist/commands/reminder-registry.js.map +1 -1
- package/dist/commands/reminder-types.d.ts +27 -0
- package/dist/commands/skill-gain-reminder.d.ts +17 -0
- package/dist/commands/skill-gain-reminder.js +32 -0
- package/dist/commands/skill-gain-reminder.js.map +1 -0
- package/dist/commands/skillgain.d.ts +12 -0
- package/dist/commands/skillgain.js +109 -0
- package/dist/commands/skillgain.js.map +1 -0
- package/dist/hook.js +204 -2
- package/dist/hook.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/opencode-plugin.bundle.js +68 -1
- package/dist/procedure.d.ts +88 -0
- package/dist/procedure.js +269 -0
- package/dist/procedure.js.map +1 -0
- package/dist/skill-landing.d.ts +88 -0
- package/dist/skill-landing.js +220 -0
- package/dist/skill-landing.js.map +1 -0
- package/package.json +1 -1
- package/skill/commander/SKILL.md +2 -2
- package/skill/greprag/SKILL.md +1 -1
- package/skill/greprag/docs/inbox-watch.md +5 -5
- package/skill/templates/chip-leader-opencode.md +98 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** greprag skillgain — the Skill Learning Loop's operator/agent verbs
|
|
3
|
+
* (docs/skill-learning-loop.md). The landing leg auto-lands reference gains;
|
|
4
|
+
* rule/scope PROPOSALS park at the operator gate and are resolved here:
|
|
5
|
+
*
|
|
6
|
+
* skillgain list pending gains for this project (all types)
|
|
7
|
+
* skillgain done <8hex> the proposal was applied to the skill (per
|
|
8
|
+
* skill-fix-conventions, diff shown + accepted)
|
|
9
|
+
* skillgain reject <8hex> the proposal was bogus — never show it again
|
|
10
|
+
*
|
|
11
|
+
* Thin client over /v1/skillgain; project resolved from cwd like every
|
|
12
|
+
* other command. Self-contained dispatcher + body (the CLI command pattern). */
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.runSkillGain = runSkillGain;
|
|
15
|
+
const project_anchor_1 = require("../project-anchor");
|
|
16
|
+
const skill_landing_1 = require("../skill-landing");
|
|
17
|
+
function getConfig() {
|
|
18
|
+
return {
|
|
19
|
+
apiUrl: process.env.GREPRAG_API_URL || 'https://api.greprag.com',
|
|
20
|
+
apiKey: process.env.GREPRAG_API_KEY || '',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const HELP = `greprag skillgain — Skill Learning Loop queue (docs/skill-learning-loop.md)
|
|
24
|
+
|
|
25
|
+
USAGE
|
|
26
|
+
greprag skillgain list Pending gains for this project (not yet landed).
|
|
27
|
+
greprag skillgain reject <8hex> The UNDO: mark a gain bogus AND remove its
|
|
28
|
+
landed artifacts (the fuse line in SKILL.md
|
|
29
|
+
+ the docs/learned/<slug>.md doc).
|
|
30
|
+
greprag skillgain done <8hex> Manually mark a pending gain resolved
|
|
31
|
+
(rarely needed — landing is automatic and a
|
|
32
|
+
digested fuse needs no marking).
|
|
33
|
+
|
|
34
|
+
Gains land automatically at session start as a briefcase (fuse line + focused
|
|
35
|
+
doc) and SELF-APPLY on the skill's next invocation.`;
|
|
36
|
+
async function runSkillGain(args) {
|
|
37
|
+
const sub = args[0];
|
|
38
|
+
const cfg = getConfig();
|
|
39
|
+
if (!cfg.apiKey) {
|
|
40
|
+
console.error('GREPRAG_API_KEY not set — run `greprag init` first.');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const anchor = (0, project_anchor_1.readAnchor)(process.cwd());
|
|
44
|
+
if (sub === 'list') {
|
|
45
|
+
const res = await fetch(`${cfg.apiUrl}/v1/skillgain/${anchor.projectId}/pending`, {
|
|
46
|
+
headers: { 'Authorization': `Bearer ${cfg.apiKey}` },
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok) {
|
|
49
|
+
console.error(`API ${res.status}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const data = await res.json();
|
|
53
|
+
const gains = data.gains || [];
|
|
54
|
+
if (gains.length === 0) {
|
|
55
|
+
console.log('No pending skill gains.');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
for (const g of gains) {
|
|
59
|
+
console.log(`[${g.nodeId}] ${g.skill} · ${g.gainType}\n ${g.text}`);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (sub === 'done' || sub === 'reject') {
|
|
64
|
+
const nodeId = (args[1] || '').toLowerCase();
|
|
65
|
+
if (!/^[0-9a-f]{8}$/.test(nodeId)) {
|
|
66
|
+
console.error(`Usage: greprag skillgain ${sub} <8hex>`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const status = sub === 'done' ? 'landed' : 'rejected';
|
|
70
|
+
const res = await fetch(`${cfg.apiUrl}/v1/skillgain/${anchor.projectId}/status`, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'Authorization': `Bearer ${cfg.apiKey}`, 'Content-Type': 'application/json' },
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
projectName: anchor.projectName,
|
|
75
|
+
updates: [{ nodeId, status }],
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
console.error(`API ${res.status}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
const data = await res.json();
|
|
83
|
+
if ((data.updated || 0) === 0) {
|
|
84
|
+
console.error(`[${nodeId}] not found in ${anchor.projectName}'s skillgain queue.`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
console.log(`[${nodeId}] → ${status}`);
|
|
88
|
+
// Reject is the UNDO: also remove any landed artifacts (the fuse line in
|
|
89
|
+
// SKILL.md + the docs/learned/<slug>.md doc). Skill name via the record.
|
|
90
|
+
if (sub === 'reject') {
|
|
91
|
+
try {
|
|
92
|
+
const gr = await fetch(`${cfg.apiUrl}/v1/skillgain/${anchor.projectId}/gain/${nodeId}`, { headers: { 'Authorization': `Bearer ${cfg.apiKey}` } });
|
|
93
|
+
if (gr.ok) {
|
|
94
|
+
const gd = await gr.json();
|
|
95
|
+
if (gd.gain?.skill) {
|
|
96
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
97
|
+
const removed = (0, skill_landing_1.removeLandedGain)(gd.gain.skill, nodeId, process.cwd(), homeDir);
|
|
98
|
+
if (removed)
|
|
99
|
+
console.log(` removed landed artifacts from ${gd.gain.skill}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch { /* best-effort — the stamp makes manual cleanup findable */ }
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
console.log(HELP);
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=skillgain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillgain.js","sourceRoot":"","sources":["../../src/commands/skillgain.ts"],"names":[],"mappings":"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"}
|
package/dist/hook.js
CHANGED
|
@@ -116,6 +116,8 @@ const coordinate_gate_1 = require("./commands/coordinate-gate");
|
|
|
116
116
|
// read, advances the poll cursor. Closes the watcher dead-window gap. All real
|
|
117
117
|
// logic lives in ./commands/inbox-drain. adr: adr/monitor-resilience.md
|
|
118
118
|
const inbox_drain_1 = require("./commands/inbox-drain");
|
|
119
|
+
const procedure_1 = require("./procedure");
|
|
120
|
+
const skill_landing_1 = require("./skill-landing");
|
|
119
121
|
const API_URL_DEFAULT = 'https://api.greprag.com';
|
|
120
122
|
const MAX_FIELD_CHARS = 500_000; // safety cap per text field
|
|
121
123
|
// ---------- Env + config ---------------------------------------------------
|
|
@@ -747,6 +749,149 @@ function capField(text) {
|
|
|
747
749
|
const originalKb = (text.length / 1000).toFixed(1);
|
|
748
750
|
return `${truncated}\n\n[truncated: original ${originalKb}KB]`;
|
|
749
751
|
}
|
|
752
|
+
// ---------- Doc pointers (docs/doc-pointer-system.md) ----------------------
|
|
753
|
+
//
|
|
754
|
+
// The Document Pointer System's detect half. No new hook: the Stop hook already
|
|
755
|
+
// sees every file the turn touched (filesTouched), so .md writes ride the same
|
|
756
|
+
// event — a deterministic path filter here, the Flash-Lite worthiness judge
|
|
757
|
+
// server-side. Best-effort end to end: a docptr failure never blocks a turn.
|
|
758
|
+
/** Deterministic pre-filter — the cheap gate BEFORE the LLM judge. Rejects the
|
|
759
|
+
* obvious never-index classes so they don't even cost a judge call: non-.md,
|
|
760
|
+
* dot-directories (.tmp-*, .claude/, .github/ — harness/scratch, not project
|
|
761
|
+
* docs), dependency/build trees, and the harness-owned CLAUDE.md/MEMORY.md
|
|
762
|
+
* (always injected anyway — indexing them is circular noise). Judge-worthy
|
|
763
|
+
* ambiguity (drafts, beats, plans) is the JUDGE's call, not this filter's. */
|
|
764
|
+
function isDocPathEligible(relPath) {
|
|
765
|
+
const p = relPath.replace(/\\/g, '/');
|
|
766
|
+
if (!p.toLowerCase().endsWith('.md'))
|
|
767
|
+
return false;
|
|
768
|
+
const segments = p.split('/');
|
|
769
|
+
const base = segments[segments.length - 1];
|
|
770
|
+
if (base === 'CLAUDE.md' || base === 'MEMORY.md')
|
|
771
|
+
return false;
|
|
772
|
+
for (const seg of segments.slice(0, -1)) {
|
|
773
|
+
if (seg.startsWith('.'))
|
|
774
|
+
return false;
|
|
775
|
+
if (seg === 'node_modules' || seg === 'dist' || seg === 'build' || seg === 'vendor')
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
return true;
|
|
779
|
+
}
|
|
780
|
+
/** Head-snippet cap posted to the judge (server re-caps defensively). */
|
|
781
|
+
const DOC_EVENT_SNIPPET_CHARS = 2_000;
|
|
782
|
+
/** Max doc events per turn (mirrors the server's MAX_EVENTS_PER_POST). */
|
|
783
|
+
const DOC_EVENTS_PER_TURN = 8;
|
|
784
|
+
/** Turn's touched files → judge-ready doc events: project-relative eligible
|
|
785
|
+
* .md paths + head snippets. Files outside cwd (scratchpad, other repos) and
|
|
786
|
+
* unreadable files (deleted later in the turn) are skipped silently. */
|
|
787
|
+
function collectDocEvents(filesTouched, cwd) {
|
|
788
|
+
const events = [];
|
|
789
|
+
const seen = new Set();
|
|
790
|
+
for (const abs of filesTouched) {
|
|
791
|
+
if (events.length >= DOC_EVENTS_PER_TURN)
|
|
792
|
+
break;
|
|
793
|
+
const rel = path.relative(cwd, abs).replace(/\\/g, '/');
|
|
794
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel))
|
|
795
|
+
continue;
|
|
796
|
+
if (!isDocPathEligible(rel) || seen.has(rel))
|
|
797
|
+
continue;
|
|
798
|
+
try {
|
|
799
|
+
const snippet = fs.readFileSync(abs, 'utf-8').slice(0, DOC_EVENT_SNIPPET_CHARS);
|
|
800
|
+
if (!snippet.trim())
|
|
801
|
+
continue;
|
|
802
|
+
seen.add(rel);
|
|
803
|
+
events.push({ path: rel, snippet });
|
|
804
|
+
}
|
|
805
|
+
catch { /* deleted/unreadable — skip */ }
|
|
806
|
+
}
|
|
807
|
+
return events;
|
|
808
|
+
}
|
|
809
|
+
/** SessionStart fetch: the live enrichment-health verdict (fix c2eb8777).
|
|
810
|
+
* Null = healthy OR unreachable — the outage announce only fires on a
|
|
811
|
+
* POSITIVE probe failure, so an API blip never cries wolf. The worker
|
|
812
|
+
* caches the probe ~5 min; this adds one cheap GET per session start. */
|
|
813
|
+
async function fetchEnrichmentDown(apiUrl, apiKey) {
|
|
814
|
+
try {
|
|
815
|
+
const res = await fetch(`${apiUrl}/v1/health/enrichment`, {
|
|
816
|
+
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
817
|
+
});
|
|
818
|
+
if (!res.ok)
|
|
819
|
+
return null;
|
|
820
|
+
const data = await res.json();
|
|
821
|
+
const e = data.enrichment;
|
|
822
|
+
if (!e || e.ok || !e.errorClass)
|
|
823
|
+
return null;
|
|
824
|
+
return { errorClass: e.errorClass, detail: e.detail, actionHint: e.actionHint };
|
|
825
|
+
}
|
|
826
|
+
catch {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
/** SessionStart pass: the Skill Learning Loop's LANDING leg
|
|
831
|
+
* (docs/skill-learning-loop.md — the write-back is CODE, not agent
|
|
832
|
+
* voluntarism). Fetch pending gains → auto-land reference-tier into each
|
|
833
|
+
* skill's docs/learnings.md → POST landed statuses back → return the report
|
|
834
|
+
* for the announce. Undefined on any error → announce silent, gains stay
|
|
835
|
+
* pending for the next session start. */
|
|
836
|
+
async function fetchAndLandSkillGains(apiUrl, apiKey, anchor, cwd) {
|
|
837
|
+
try {
|
|
838
|
+
const res = await fetch(`${apiUrl}/v1/skillgain/${anchor.projectId}/pending`, {
|
|
839
|
+
headers: { 'Authorization': `Bearer ${apiKey}` },
|
|
840
|
+
});
|
|
841
|
+
if (!res.ok)
|
|
842
|
+
return undefined;
|
|
843
|
+
const data = await res.json();
|
|
844
|
+
const pending = (data.gains || []).filter(g => g.nodeId && g.skill && g.text);
|
|
845
|
+
if (pending.length === 0)
|
|
846
|
+
return undefined;
|
|
847
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
848
|
+
const { report, statusUpdates } = (0, skill_landing_1.landPendingGains)(pending, cwd, homeDir, anchor.projectName);
|
|
849
|
+
if (statusUpdates.length > 0) {
|
|
850
|
+
try {
|
|
851
|
+
await fetch(`${apiUrl}/v1/skillgain/${anchor.projectId}/status`, {
|
|
852
|
+
method: 'POST',
|
|
853
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
854
|
+
body: JSON.stringify({ projectName: anchor.projectName, updates: statusUpdates }),
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
catch { /* statuses re-sync next start — the stamp keeps lands idempotent */ }
|
|
858
|
+
}
|
|
859
|
+
return report.landed.length > 0 ? report : undefined;
|
|
860
|
+
}
|
|
861
|
+
catch {
|
|
862
|
+
return undefined;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
/** SessionStart fetch: ONE reconcile+list round trip. Ships the project's
|
|
866
|
+
* tracked .md set (`git ls-files`) so vanished docs go stale server-side, and
|
|
867
|
+
* returns the active pointers for the announce. [] on any error → the
|
|
868
|
+
* announce stays silent (never blocks a session start). */
|
|
869
|
+
async function fetchDocPointers(apiUrl, apiKey, anchor, cwd) {
|
|
870
|
+
try {
|
|
871
|
+
let presentPaths;
|
|
872
|
+
try {
|
|
873
|
+
const out = (0, proc_1.safeExecSync)('git ls-files -- "*.md"', {
|
|
874
|
+
cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true,
|
|
875
|
+
});
|
|
876
|
+
presentPaths = out.split('\n').map(s => s.trim()).filter(s => isDocPathEligible(s));
|
|
877
|
+
}
|
|
878
|
+
catch { /* not a git repo — list-only reconcile */ }
|
|
879
|
+
const res = await fetch(`${apiUrl}/v1/docptr/${anchor.projectId}/reconcile`, {
|
|
880
|
+
method: 'POST',
|
|
881
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
|
|
882
|
+
body: JSON.stringify({ projectName: anchor.projectName, presentPaths }),
|
|
883
|
+
});
|
|
884
|
+
if (!res.ok)
|
|
885
|
+
return [];
|
|
886
|
+
const data = await res.json();
|
|
887
|
+
return (data.pointers || [])
|
|
888
|
+
.filter(p => p.path && p.hook)
|
|
889
|
+
.map(p => ({ path: p.path, hook: p.hook }));
|
|
890
|
+
}
|
|
891
|
+
catch {
|
|
892
|
+
return [];
|
|
893
|
+
}
|
|
894
|
+
}
|
|
750
895
|
// ---------- Store ---------------------------------------------------------
|
|
751
896
|
async function store(input, source = 'claude-code') {
|
|
752
897
|
const cwd = input.cwd || process.cwd();
|
|
@@ -828,6 +973,19 @@ async function store(input, source = 'claude-code') {
|
|
|
828
973
|
source,
|
|
829
974
|
provenance,
|
|
830
975
|
});
|
|
976
|
+
// Doc Pointer System detect half (docs/doc-pointer-system.md): .md writes this
|
|
977
|
+
// turn → one best-effort event POST; the worthiness judge runs server-side in
|
|
978
|
+
// waitUntil. After the turn POST (memory capture always wins), never throws.
|
|
979
|
+
const docEvents = collectDocEvents(turn.filesTouched, cwd);
|
|
980
|
+
if (docEvents.length > 0) {
|
|
981
|
+
try {
|
|
982
|
+
await apiCall(`${cfg.apiUrl}/v1/docptr/${anchor.projectId}/events`, cfg.apiKey, {
|
|
983
|
+
projectName: anchor.projectName,
|
|
984
|
+
events: docEvents,
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
catch { /* best-effort — a docptr outage never fails the Stop hook */ }
|
|
988
|
+
}
|
|
831
989
|
}
|
|
832
990
|
// ---------- Main ---------------------------------------------------------
|
|
833
991
|
// ---------- Recap (SessionStart) ------------------------------------------
|
|
@@ -1143,6 +1301,17 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1143
1301
|
// tap, so the corpus module can tell the agent to search them before answering
|
|
1144
1302
|
// from training data. Best-effort; empty → the announce stays silent.
|
|
1145
1303
|
const corpusApiDocs = await fetchCorpusApiDocs(cfg.apiUrl, cfg.apiKey);
|
|
1304
|
+
// Doc-pointer announce signal (docs/doc-pointer-system.md) — one reconcile+list
|
|
1305
|
+
// round trip: vanished docs go stale server-side, active pointers come back for
|
|
1306
|
+
// the "core documents" block. Best-effort; empty → the announce stays silent.
|
|
1307
|
+
const docPointers = await fetchDocPointers(cfg.apiUrl, cfg.apiKey, anchor, cwd);
|
|
1308
|
+
// Enrichment-health signal (fix c2eb8777) — the live Gemini probe verdict.
|
|
1309
|
+
// Null when healthy or unreachable; a positive failure fires the outage announce.
|
|
1310
|
+
const enrichmentDown = await fetchEnrichmentDown(cfg.apiUrl, cfg.apiKey);
|
|
1311
|
+
// Skill Learning Loop landing leg (docs/skill-learning-loop.md): pending
|
|
1312
|
+
// gains → auto-land references into learnings docs, hold rule/scope
|
|
1313
|
+
// proposals for the announce. Best-effort; undefined → announce silent.
|
|
1314
|
+
const skillGains = await fetchAndLandSkillGains(cfg.apiUrl, cfg.apiKey, anchor, cwd);
|
|
1146
1315
|
// Assistant doctrine auto-load — fires ONLY for the flagged assistant project
|
|
1147
1316
|
// (isAssistantProject), so a normal project sees zero change. The hook owns the
|
|
1148
1317
|
// doctrine-file read; the assistant-doctrine module routes the text. adr: adr/assistant-role.md
|
|
@@ -1175,6 +1344,9 @@ async function recap(input, mode = 'plain', opts = {}) {
|
|
|
1175
1344
|
setupWarning,
|
|
1176
1345
|
assistantDoctrine,
|
|
1177
1346
|
corpusApiDocs,
|
|
1347
|
+
docPointers,
|
|
1348
|
+
enrichmentDown,
|
|
1349
|
+
skillGains,
|
|
1178
1350
|
updateAvailable,
|
|
1179
1351
|
};
|
|
1180
1352
|
let announceReg = mechanicKilled() ? reminder_registry_1.REGISTRY.filter(m => m.id !== 'mechanic-friction') : reminder_registry_1.REGISTRY;
|
|
@@ -1289,6 +1461,31 @@ function codexSubagentStart(input) {
|
|
|
1289
1461
|
if (context)
|
|
1290
1462
|
writeAdditionalContext(input.hook_event_name || 'SubagentStart', context);
|
|
1291
1463
|
}
|
|
1464
|
+
/** UserPromptSubmit — Procedure System REPLAY leg (docs/procedure-system.md).
|
|
1465
|
+
* Match the prompt against the project's procedure store; on a hit inject the
|
|
1466
|
+
* recipe + gate as additionalContext BEFORE the agent moves, so an operational
|
|
1467
|
+
* verb runs cleanly instead of by trial-and-error. Pure-local (no network on
|
|
1468
|
+
* the hot path — reads the file beside the matchset cache), additive, and
|
|
1469
|
+
* fail-open: a miss or any error never blocks the turn. The Tier-1 intent guard
|
|
1470
|
+
* (in matchProcedure) suppresses keyword mention-not-request false-fires. */
|
|
1471
|
+
async function procedureCheck(input) {
|
|
1472
|
+
try {
|
|
1473
|
+
const prompt = typeof input.prompt === 'string' ? input.prompt : '';
|
|
1474
|
+
if (!prompt.trim())
|
|
1475
|
+
return;
|
|
1476
|
+
const anchor = (0, project_anchor_1.readAnchor)(input.cwd || process.cwd());
|
|
1477
|
+
if (!anchor.projectId)
|
|
1478
|
+
return;
|
|
1479
|
+
const store = (0, procedure_1.readProcedureStore)(anchor.projectId);
|
|
1480
|
+
if (!store.procedures.length)
|
|
1481
|
+
return;
|
|
1482
|
+
const m = (0, procedure_1.matchProcedure)(prompt, store);
|
|
1483
|
+
if (!m)
|
|
1484
|
+
return;
|
|
1485
|
+
writeAdditionalContext(input.hook_event_name || 'UserPromptSubmit', (0, procedure_1.buildProcedureInjection)(m.procedure));
|
|
1486
|
+
}
|
|
1487
|
+
catch { /* fail-open — a procedure miss never blocks the turn */ }
|
|
1488
|
+
}
|
|
1292
1489
|
async function notify(input, source = 'claude-code') {
|
|
1293
1490
|
if (source === 'codex')
|
|
1294
1491
|
cacheCodexPrompt(input);
|
|
@@ -1738,7 +1935,7 @@ async function main() {
|
|
|
1738
1935
|
const subcommand = process.argv[2];
|
|
1739
1936
|
const validSubs = new Set([
|
|
1740
1937
|
'store', 'recap', 'recompact', 'notify', 'mail', 'friction-reminder', 'session-id', 'pre-spawn-check', 'armcheck',
|
|
1741
|
-
'collision-check', 'drain', 'coordinate-gate',
|
|
1938
|
+
'collision-check', 'drain', 'coordinate-gate', 'procedure-check',
|
|
1742
1939
|
'guard', 'guard-refresh', 'guard-flush',
|
|
1743
1940
|
'context-probe', 'context-check', 'crush-wrap',
|
|
1744
1941
|
'pre-compact', 'archive-pointer',
|
|
@@ -1746,7 +1943,7 @@ async function main() {
|
|
|
1746
1943
|
'codex-permission-context', 'codex-subagent-start',
|
|
1747
1944
|
]);
|
|
1748
1945
|
if (!validSubs.has(subcommand)) {
|
|
1749
|
-
process.stderr.write(`Usage: greprag-hook <store|recap|recompact|notify|mail|friction-reminder|session-id|pre-spawn-check|armcheck|collision-check|drain|coordinate-gate|guard|guard-refresh|guard-flush|context-probe|context-check|crush-wrap|pre-compact|archive-pointer|codex-store|codex-notify|codex-inbox|codex-recap|codex-permission-context|codex-subagent-start>\n`);
|
|
1946
|
+
process.stderr.write(`Usage: greprag-hook <store|recap|recompact|notify|mail|friction-reminder|procedure-check|session-id|pre-spawn-check|armcheck|collision-check|drain|coordinate-gate|guard|guard-refresh|guard-flush|context-probe|context-check|crush-wrap|pre-compact|archive-pointer|codex-store|codex-notify|codex-inbox|codex-recap|codex-permission-context|codex-subagent-start>\n`);
|
|
1750
1947
|
process.exit(1);
|
|
1751
1948
|
}
|
|
1752
1949
|
let input = {};
|
|
@@ -1781,6 +1978,11 @@ async function main() {
|
|
|
1781
1978
|
else if (subcommand === 'mail') {
|
|
1782
1979
|
await mail(input);
|
|
1783
1980
|
}
|
|
1981
|
+
else if (subcommand === 'procedure-check') {
|
|
1982
|
+
// UserPromptSubmit — Procedure System REPLAY: inject a matched recipe + gate
|
|
1983
|
+
// before the agent moves. Local, additive, fail-open. docs/procedure-system.md
|
|
1984
|
+
await procedureCheck(input);
|
|
1985
|
+
}
|
|
1784
1986
|
else if (subcommand === 'friction-reminder') {
|
|
1785
1987
|
// UserPromptSubmit — the active Mechanic's dynamic friction reminder (beside
|
|
1786
1988
|
// the arm directive). Stress-driven tier: silent/ambient/nudge/nag. Reads the
|