greprag 5.50.0 → 5.52.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 +2 -0
- package/dist/commands/doc-pointer-reminder.js +16 -2
- package/dist/commands/doc-pointer-reminder.js.map +1 -1
- package/dist/commands/docptr.d.ts +17 -0
- package/dist/commands/docptr.js +190 -0
- package/dist/commands/docptr.js.map +1 -0
- package/dist/commands/load.d.ts +8 -5
- package/dist/commands/load.js +81 -10
- package/dist/commands/load.js.map +1 -1
- package/dist/commands/reminder-registry.js +3 -1
- package/dist/commands/reminder-registry.js.map +1 -1
- package/dist/commands/reminder-types.d.ts +10 -0
- package/dist/commands/skill-mirror-reminder.d.ts +15 -0
- package/dist/commands/skill-mirror-reminder.js +33 -0
- package/dist/commands/skill-mirror-reminder.js.map +1 -0
- package/dist/hook.js +284 -19
- package/dist/hook.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/opencode-plugin.bundle.js +37 -3
- package/dist/procedure-watch.d.ts +153 -0
- package/dist/procedure-watch.js +349 -0
- package/dist/procedure-watch.js.map +1 -0
- package/dist/skill-mirror-client.d.ts +37 -0
- package/dist/skill-mirror-client.js +172 -0
- package/dist/skill-mirror-client.js.map +1 -0
- package/package.json +1 -1
|
@@ -1840,10 +1840,20 @@ function buildDocPointerAnnounce(pointers) {
|
|
|
1840
1840
|
return null;
|
|
1841
1841
|
const shown = pointers.slice(0, ANNOUNCE_CAP);
|
|
1842
1842
|
const more = pointers.length > shown.length ? ` (+${pointers.length - shown.length} more registered)` : "";
|
|
1843
|
+
const line = (p) => {
|
|
1844
|
+
if (p.buried)
|
|
1845
|
+
return `\u26A0 ${p.path} \u2014 ${p.hook} [likely stale: ${p.decayNote} \u2014 verify or remove]`;
|
|
1846
|
+
if (p.decayNote)
|
|
1847
|
+
return `\u2022 ${p.path} \u2014 ${p.hook} [${p.decayNote}]`;
|
|
1848
|
+
return `\u2022 ${p.path} \u2014 ${p.hook}`;
|
|
1849
|
+
};
|
|
1850
|
+
const buriedN = shown.filter((p) => p.buried).length;
|
|
1851
|
+
const staleFooter = buriedN > 0 ? `
|
|
1852
|
+
${buriedN} doc(s) flagged likely-stale (cited files vanished) \u2014 confirm or remove them.` : "";
|
|
1843
1853
|
return [
|
|
1844
1854
|
`[greprag docs \u2014 core documents on file for this project (auto-registered)${more}:]`,
|
|
1845
|
-
...shown.map(
|
|
1846
|
-
"One of these covers your topic? READ IT before re-deriving what it already answers."
|
|
1855
|
+
...shown.map(line),
|
|
1856
|
+
"One of these covers your topic? READ IT before re-deriving what it already answers." + staleFooter
|
|
1847
1857
|
].join("\n");
|
|
1848
1858
|
}
|
|
1849
1859
|
var docPointerAnnounceModule = {
|
|
@@ -2078,6 +2088,28 @@ var skillGainAnnounceModule = {
|
|
|
2078
2088
|
reminder: () => null
|
|
2079
2089
|
};
|
|
2080
2090
|
|
|
2091
|
+
// src/commands/skill-mirror-reminder.ts
|
|
2092
|
+
function buildSkillMirrorAnnounce(m) {
|
|
2093
|
+
if (!m || m.count <= 0)
|
|
2094
|
+
return null;
|
|
2095
|
+
const sample = m.names.slice(0, 4).join(", ");
|
|
2096
|
+
const more = m.count > 4 ? ", \u2026" : "";
|
|
2097
|
+
return [
|
|
2098
|
+
`[greprag skills \u2014 ${m.count} of your skills travel with the harness (auto-mirrored from use, always fresh): ${sample}${more}.]`,
|
|
2099
|
+
"Any machine or harness with the greprag CLI can use them \u2014 `greprag load` to browse, `greprag load <skill>` to read one inline. No ~/.claude/skills needed."
|
|
2100
|
+
].join("\n");
|
|
2101
|
+
}
|
|
2102
|
+
var skillMirrorAnnounceModule = {
|
|
2103
|
+
id: "skill-mirror-announce",
|
|
2104
|
+
source: "prompt",
|
|
2105
|
+
dependsOn: ["load-primer"],
|
|
2106
|
+
// references `greprag load` — the primer establishes it
|
|
2107
|
+
detect: (_env) => ({ tier: "silent" }),
|
|
2108
|
+
// announce-only
|
|
2109
|
+
announce: (env) => buildSkillMirrorAnnounce(env.mirroredSkills),
|
|
2110
|
+
reminder: () => null
|
|
2111
|
+
};
|
|
2112
|
+
|
|
2081
2113
|
// src/commands/reminder-registry.ts
|
|
2082
2114
|
var REGISTRY = [
|
|
2083
2115
|
inboxPrimerModule,
|
|
@@ -2087,7 +2119,9 @@ var REGISTRY = [
|
|
|
2087
2119
|
docPointerAnnounceModule,
|
|
2088
2120
|
// announce-only "core documents" list (docs/doc-pointer-system.md)
|
|
2089
2121
|
skillGainAnnounceModule,
|
|
2090
|
-
// announce-only skill-gain landings
|
|
2122
|
+
// announce-only skill-gain landings (docs/skill-learning-loop.md)
|
|
2123
|
+
skillMirrorAnnounceModule,
|
|
2124
|
+
// one-line Tier-3 pointer: your skills travel via greprag load (docs/load-system.md)
|
|
2091
2125
|
setupWarningModule,
|
|
2092
2126
|
versionUpgradeModule,
|
|
2093
2127
|
// Deficiency-gated announce — silent unless a newer release exists
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/** Procedure watch — the Procedure System's span-watch state + close logic
|
|
2
|
+
* (docs/procedure-system.md §V1 LEARN-leg build spec, component 1).
|
|
3
|
+
*
|
|
4
|
+
* The unit of learning is a WATCH WINDOW: the UserPromptSubmit trigger opens
|
|
5
|
+
* it, the Stop hook observes each turn inside it, negative continuity closes
|
|
6
|
+
* it ("is the <verb> procedure still live?" — cessation, never completion).
|
|
7
|
+
* One JSON file beside the procedure store; ONE active watch at a time (v1).
|
|
8
|
+
*
|
|
9
|
+
* PURE open/advance/close (openWatch / buildTurnDigest / advanceWatch /
|
|
10
|
+
* liveSpan / applyDistillTransition) is split from the file I/O (read/write/
|
|
11
|
+
* clear) — the procedure.ts shape — so hysteresis, the watchdog, and the
|
|
12
|
+
* transition table are unit-testable without a hook.
|
|
13
|
+
*
|
|
14
|
+
* Endpoint-detection invariants (§Endpoint detection):
|
|
15
|
+
* - hysteresis: one OFF turn never closes (a tangent can't truncate the
|
|
16
|
+
* span); close at offStreak ≥ 2.
|
|
17
|
+
* - the endpoint stamps at the LAST LIVE turn, not the first dead one —
|
|
18
|
+
* span = observations[0 .. lastLiveTurn].
|
|
19
|
+
* - judge unreachable → offStreak untouched (the watch extends); the
|
|
20
|
+
* max-turn watchdog is the backstop, never the primary mechanism. */
|
|
21
|
+
import { type Procedure, type ProcedureStore, type ProcedureMatch } from './procedure';
|
|
22
|
+
export type WatchPhase = 'LEARN' | 'REPLAY';
|
|
23
|
+
/** One observed turn, compact (≤1.5K serialized — the digest cap bounds both
|
|
24
|
+
* the per-turn observe POST and the close-time distill call). */
|
|
25
|
+
export interface ProcedureTurnDigest {
|
|
26
|
+
/** 1-based turn index within the watch. */
|
|
27
|
+
turn: number;
|
|
28
|
+
/** User-prompt head. */
|
|
29
|
+
user: string;
|
|
30
|
+
/** Bash commands the turn ran, each with a short output-tail exit hint. */
|
|
31
|
+
commands: string[];
|
|
32
|
+
/** Files the turn touched (project-relative where possible). */
|
|
33
|
+
files: string[];
|
|
34
|
+
/** Turn status: completed | interrupted | errored. */
|
|
35
|
+
status: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ProcedureWatch {
|
|
38
|
+
verb: string;
|
|
39
|
+
phase: WatchPhase;
|
|
40
|
+
openedAt: string;
|
|
41
|
+
/** Turns observed so far (== observations.length). */
|
|
42
|
+
turnCount: number;
|
|
43
|
+
observations: ProcedureTurnDigest[];
|
|
44
|
+
/** The last turn the judge called LIVE — where the endpoint stamps.
|
|
45
|
+
* 0 = never live. */
|
|
46
|
+
lastLiveTurn: number;
|
|
47
|
+
/** Consecutive not-live turns. Close at ≥ WATCH_OFF_STREAK_CLOSE. */
|
|
48
|
+
offStreak: number;
|
|
49
|
+
}
|
|
50
|
+
/** Watchdog backstop: a watch never observes past this many turns — the next
|
|
51
|
+
* turn aborts it (log a fix-queue smell, never distill). */
|
|
52
|
+
export declare const WATCH_MAX_TURNS = 15;
|
|
53
|
+
/** Hysteresis: consecutive OFF turns required to close (a one-turn tangent
|
|
54
|
+
* can't truncate the span). */
|
|
55
|
+
export declare const WATCH_OFF_STREAK_CLOSE = 2;
|
|
56
|
+
/** Per-turn digest cap, serialized. */
|
|
57
|
+
export declare const DIGEST_TOTAL_CAP = 1500;
|
|
58
|
+
/** User-prompt head cap inside the digest. */
|
|
59
|
+
export declare const DIGEST_USER_CAP = 300;
|
|
60
|
+
/** The Tier-1 trigger set for OPENING A LEARN WATCH, independent of the store
|
|
61
|
+
* (docs/procedure-system.md §Tier 1: deploy / push / release / ship / build /
|
|
62
|
+
* test — `ship` phrases map to the `release` verb, matching GIT_SEED_SET).
|
|
63
|
+
* `build` + `test` have no seed: the clean LEARN proof case. `push` is
|
|
64
|
+
* destructive — it sits here only so the opener can NAME the refusal; a LEARN
|
|
65
|
+
* watch never opens for it (the seed is the canon even when stale). Entries
|
|
66
|
+
* are Procedure-shaped (steps empty — vocabulary, not recipes) so the same
|
|
67
|
+
* matcher + intent guard run over them. */
|
|
68
|
+
export declare const TIER1_LEARN_TRIGGERS: Procedure[];
|
|
69
|
+
/** Match a prompt against the Tier-1 LEARN vocabulary (same matcher + intent
|
|
70
|
+
* guard as the store path). PURE. */
|
|
71
|
+
export declare function matchLearnTrigger(prompt: string): ProcedureMatch | null;
|
|
72
|
+
/** Is this verb destructive — per the store's procedure OR the Tier-1
|
|
73
|
+
* vocabulary? Destructive verbs are never learn-writable and a LEARN watch
|
|
74
|
+
* never opens for them. PURE. */
|
|
75
|
+
export declare function isDestructiveVerb(verb: string, store: ProcedureStore): boolean;
|
|
76
|
+
/** The trigger phrases a LEARNED procedure should match on next time — the
|
|
77
|
+
* store procedure's own triggers when it exists (stale re-learn keeps them),
|
|
78
|
+
* else the Tier-1 vocabulary's, else the bare verb. PURE. */
|
|
79
|
+
export declare function learnTriggersForVerb(verb: string, store: ProcedureStore): string[];
|
|
80
|
+
export declare function openWatch(verb: string, phase: WatchPhase, openedAt?: string): ProcedureWatch;
|
|
81
|
+
/** What the digest builder needs from the parsed turn — a structural subset of
|
|
82
|
+
* the hook's ParsedTurn (Bash briefs carry the command; _output the stdout). */
|
|
83
|
+
export interface DigestSourceTurn {
|
|
84
|
+
userPrompt: string;
|
|
85
|
+
toolCalls: Array<{
|
|
86
|
+
name: string;
|
|
87
|
+
brief?: string;
|
|
88
|
+
_output?: string;
|
|
89
|
+
}>;
|
|
90
|
+
filesTouched: string[];
|
|
91
|
+
status: string;
|
|
92
|
+
}
|
|
93
|
+
/** Build one compact TurnDigest: user-prompt head ≤300 chars, Bash commands
|
|
94
|
+
* (+ a short output-tail exit hint) from toolCalls, files touched, turn
|
|
95
|
+
* status — ≤1.5K serialized total. PURE — unit-tested. */
|
|
96
|
+
export declare function buildTurnDigest(turn: DigestSourceTurn, turnNumber: number): ProcedureTurnDigest;
|
|
97
|
+
export type WatchDisposition = 'continue' | 'close' | 'watchdog';
|
|
98
|
+
/** Would observing one more turn cross the watchdog? The caller checks this
|
|
99
|
+
* BEFORE the judge call so the aborting turn costs nothing. PURE. */
|
|
100
|
+
export declare function willExceedWatchdog(watch: ProcedureWatch): boolean;
|
|
101
|
+
/** Advance the watch by one observed turn. Appends the digest, then applies
|
|
102
|
+
* the continuity verdict per §Endpoint detection:
|
|
103
|
+
* live=true → stamp lastLiveTurn, reset offStreak
|
|
104
|
+
* live=false → offStreak++
|
|
105
|
+
* live=null → judge unreachable: offStreak UNTOUCHED (the watch extends;
|
|
106
|
+
* the watchdog is the backstop)
|
|
107
|
+
* Dispositions: 'watchdog' when the turn count exceeds the cap (defensive —
|
|
108
|
+
* callers should pre-check willExceedWatchdog), 'close' at
|
|
109
|
+
* offStreak ≥ WATCH_OFF_STREAK_CLOSE, else 'continue'. PURE — unit-tested. */
|
|
110
|
+
export declare function advanceWatch(watch: ProcedureWatch, digest: ProcedureTurnDigest, live: boolean | null): {
|
|
111
|
+
watch: ProcedureWatch;
|
|
112
|
+
disposition: WatchDisposition;
|
|
113
|
+
};
|
|
114
|
+
/** The live span — the endpoint stamps at the last LIVE turn, so trailing
|
|
115
|
+
* not-live turns never enter the artifact. Span = observations[0 ..
|
|
116
|
+
* lastLiveTurn]. Empty when the watch was never live (nothing to distill —
|
|
117
|
+
* never register garbage). PURE. */
|
|
118
|
+
export declare function liveSpan(watch: ProcedureWatch): ProcedureTurnDigest[];
|
|
119
|
+
export interface DistilledFields {
|
|
120
|
+
steps: string;
|
|
121
|
+
endpoint: string;
|
|
122
|
+
caveats?: string;
|
|
123
|
+
}
|
|
124
|
+
export type TransitionAction = 'learned' | 'confirmed' | 'relearned' | 'discarded';
|
|
125
|
+
/** Apply the spec's transition table (component 6) to the procedure store:
|
|
126
|
+
*
|
|
127
|
+
* LEARN | any (span ended) | status=learned, steps/endpoint/caveats from distill
|
|
128
|
+
* REPLAY | clean | status=confirmed (steps untouched)
|
|
129
|
+
* REPLAY | floundered | re-learn in place: distill the failed span → learned
|
|
130
|
+
* any | distill unreadable / judge down | no write; watch discarded
|
|
131
|
+
*
|
|
132
|
+
* Each row gates on the fields it actually consumes: LEARN and
|
|
133
|
+
* REPLAY-floundered need the distilled fields; REPLAY-clean needs the
|
|
134
|
+
* outcome. A missing required field = the unreadable/judge-down row → no
|
|
135
|
+
* write. All writes flow through upsertProcedure, whose destructive guard
|
|
136
|
+
* already refuses to overwrite a `push` seed (never weakened here).
|
|
137
|
+
* PURE — unit-tested. Returns the (possibly unchanged) store + what happened. */
|
|
138
|
+
export declare function applyDistillTransition(store: ProcedureStore, watch: Pick<ProcedureWatch, 'verb' | 'phase'>, outcome: 'clean' | 'floundered' | null, distilled: DistilledFields | null, fallbackTriggers: string[]): {
|
|
139
|
+
store: ProcedureStore;
|
|
140
|
+
action: TransitionAction;
|
|
141
|
+
};
|
|
142
|
+
export declare function procedureWatchPath(projectId: string): string;
|
|
143
|
+
/** The Stop-hook observer's zero-cost gate: no file → no watch → no work. */
|
|
144
|
+
export declare function hasProcedureWatch(projectId: string): boolean;
|
|
145
|
+
/** Read the active watch. Null on any miss/corruption — fail-open; the caller
|
|
146
|
+
* clears the file so a corrupt watch can't wedge the observer. */
|
|
147
|
+
export declare function readProcedureWatch(projectId: string): ProcedureWatch | null;
|
|
148
|
+
export declare function writeProcedureWatch(projectId: string, watch: ProcedureWatch): void;
|
|
149
|
+
export declare function clearProcedureWatch(projectId: string): void;
|
|
150
|
+
/** Open a watch iff none is active (ONE active watch at a time, v1 — an
|
|
151
|
+
* in-flight watch is never replaced by a new trigger). Returns whether a
|
|
152
|
+
* watch was opened. */
|
|
153
|
+
export declare function openWatchIfIdle(projectId: string, verb: string, phase: WatchPhase): boolean;
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Procedure watch — the Procedure System's span-watch state + close logic
|
|
3
|
+
* (docs/procedure-system.md §V1 LEARN-leg build spec, component 1).
|
|
4
|
+
*
|
|
5
|
+
* The unit of learning is a WATCH WINDOW: the UserPromptSubmit trigger opens
|
|
6
|
+
* it, the Stop hook observes each turn inside it, negative continuity closes
|
|
7
|
+
* it ("is the <verb> procedure still live?" — cessation, never completion).
|
|
8
|
+
* One JSON file beside the procedure store; ONE active watch at a time (v1).
|
|
9
|
+
*
|
|
10
|
+
* PURE open/advance/close (openWatch / buildTurnDigest / advanceWatch /
|
|
11
|
+
* liveSpan / applyDistillTransition) is split from the file I/O (read/write/
|
|
12
|
+
* clear) — the procedure.ts shape — so hysteresis, the watchdog, and the
|
|
13
|
+
* transition table are unit-testable without a hook.
|
|
14
|
+
*
|
|
15
|
+
* Endpoint-detection invariants (§Endpoint detection):
|
|
16
|
+
* - hysteresis: one OFF turn never closes (a tangent can't truncate the
|
|
17
|
+
* span); close at offStreak ≥ 2.
|
|
18
|
+
* - the endpoint stamps at the LAST LIVE turn, not the first dead one —
|
|
19
|
+
* span = observations[0 .. lastLiveTurn].
|
|
20
|
+
* - judge unreachable → offStreak untouched (the watch extends); the
|
|
21
|
+
* max-turn watchdog is the backstop, never the primary mechanism. */
|
|
22
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
25
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
26
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(o, k2, desc);
|
|
29
|
+
}) : (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
o[k2] = m[k];
|
|
32
|
+
}));
|
|
33
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
34
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
35
|
+
}) : function(o, v) {
|
|
36
|
+
o["default"] = v;
|
|
37
|
+
});
|
|
38
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
39
|
+
var ownKeys = function(o) {
|
|
40
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
41
|
+
var ar = [];
|
|
42
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
43
|
+
return ar;
|
|
44
|
+
};
|
|
45
|
+
return ownKeys(o);
|
|
46
|
+
};
|
|
47
|
+
return function (mod) {
|
|
48
|
+
if (mod && mod.__esModule) return mod;
|
|
49
|
+
var result = {};
|
|
50
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
51
|
+
__setModuleDefault(result, mod);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
})();
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.TIER1_LEARN_TRIGGERS = exports.DIGEST_USER_CAP = exports.DIGEST_TOTAL_CAP = exports.WATCH_OFF_STREAK_CLOSE = exports.WATCH_MAX_TURNS = void 0;
|
|
57
|
+
exports.matchLearnTrigger = matchLearnTrigger;
|
|
58
|
+
exports.isDestructiveVerb = isDestructiveVerb;
|
|
59
|
+
exports.learnTriggersForVerb = learnTriggersForVerb;
|
|
60
|
+
exports.openWatch = openWatch;
|
|
61
|
+
exports.buildTurnDigest = buildTurnDigest;
|
|
62
|
+
exports.willExceedWatchdog = willExceedWatchdog;
|
|
63
|
+
exports.advanceWatch = advanceWatch;
|
|
64
|
+
exports.liveSpan = liveSpan;
|
|
65
|
+
exports.applyDistillTransition = applyDistillTransition;
|
|
66
|
+
exports.procedureWatchPath = procedureWatchPath;
|
|
67
|
+
exports.hasProcedureWatch = hasProcedureWatch;
|
|
68
|
+
exports.readProcedureWatch = readProcedureWatch;
|
|
69
|
+
exports.writeProcedureWatch = writeProcedureWatch;
|
|
70
|
+
exports.clearProcedureWatch = clearProcedureWatch;
|
|
71
|
+
exports.openWatchIfIdle = openWatchIfIdle;
|
|
72
|
+
const path = __importStar(require("path"));
|
|
73
|
+
const fs = __importStar(require("fs"));
|
|
74
|
+
const procedure_1 = require("./procedure");
|
|
75
|
+
/** Watchdog backstop: a watch never observes past this many turns — the next
|
|
76
|
+
* turn aborts it (log a fix-queue smell, never distill). */
|
|
77
|
+
exports.WATCH_MAX_TURNS = 15;
|
|
78
|
+
/** Hysteresis: consecutive OFF turns required to close (a one-turn tangent
|
|
79
|
+
* can't truncate the span). */
|
|
80
|
+
exports.WATCH_OFF_STREAK_CLOSE = 2;
|
|
81
|
+
/** Per-turn digest cap, serialized. */
|
|
82
|
+
exports.DIGEST_TOTAL_CAP = 1_500;
|
|
83
|
+
/** User-prompt head cap inside the digest. */
|
|
84
|
+
exports.DIGEST_USER_CAP = 300;
|
|
85
|
+
const DIGEST_MAX_COMMANDS = 6;
|
|
86
|
+
const DIGEST_COMMAND_CAP = 180;
|
|
87
|
+
const DIGEST_EXIT_HINT_CAP = 80;
|
|
88
|
+
const DIGEST_MAX_FILES = 8;
|
|
89
|
+
// ---------- Tier-1 LEARN trigger vocabulary (PURE data) -----------------------
|
|
90
|
+
/** The Tier-1 trigger set for OPENING A LEARN WATCH, independent of the store
|
|
91
|
+
* (docs/procedure-system.md §Tier 1: deploy / push / release / ship / build /
|
|
92
|
+
* test — `ship` phrases map to the `release` verb, matching GIT_SEED_SET).
|
|
93
|
+
* `build` + `test` have no seed: the clean LEARN proof case. `push` is
|
|
94
|
+
* destructive — it sits here only so the opener can NAME the refusal; a LEARN
|
|
95
|
+
* watch never opens for it (the seed is the canon even when stale). Entries
|
|
96
|
+
* are Procedure-shaped (steps empty — vocabulary, not recipes) so the same
|
|
97
|
+
* matcher + intent guard run over them. */
|
|
98
|
+
exports.TIER1_LEARN_TRIGGERS = [
|
|
99
|
+
{ verb: 'deploy', triggers: ['deploy', 'deploy the api', 'deploy the worker', 'redeploy'], steps: '', status: 'seeded' },
|
|
100
|
+
{ verb: 'push', triggers: ['push', 'git push', 'push to remote', 'push it up', 'push upstream'], steps: '', status: 'seeded', destructive: true },
|
|
101
|
+
{ verb: 'release', triggers: ['release', 'ship', 'ship it', 'ship a release', 'cut a release', 'publish the release', 'do a release'], steps: '', status: 'seeded' },
|
|
102
|
+
{ verb: 'build', triggers: ['build', 'rebuild', 'compile', 'run the build'], steps: '', status: 'seeded' },
|
|
103
|
+
{ verb: 'test', triggers: ['test', 'tests', 'run the tests', 'run tests'], steps: '', status: 'seeded' },
|
|
104
|
+
];
|
|
105
|
+
/** Match a prompt against the Tier-1 LEARN vocabulary (same matcher + intent
|
|
106
|
+
* guard as the store path). PURE. */
|
|
107
|
+
function matchLearnTrigger(prompt) {
|
|
108
|
+
return (0, procedure_1.matchProcedure)(prompt, {
|
|
109
|
+
version: procedure_1.PROCEDURE_STORE_VERSION,
|
|
110
|
+
procedures: exports.TIER1_LEARN_TRIGGERS,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** Is this verb destructive — per the store's procedure OR the Tier-1
|
|
114
|
+
* vocabulary? Destructive verbs are never learn-writable and a LEARN watch
|
|
115
|
+
* never opens for them. PURE. */
|
|
116
|
+
function isDestructiveVerb(verb, store) {
|
|
117
|
+
if (store.procedures.some(p => p.verb === verb && p.destructive))
|
|
118
|
+
return true;
|
|
119
|
+
return exports.TIER1_LEARN_TRIGGERS.some(p => p.verb === verb && p.destructive);
|
|
120
|
+
}
|
|
121
|
+
/** The trigger phrases a LEARNED procedure should match on next time — the
|
|
122
|
+
* store procedure's own triggers when it exists (stale re-learn keeps them),
|
|
123
|
+
* else the Tier-1 vocabulary's, else the bare verb. PURE. */
|
|
124
|
+
function learnTriggersForVerb(verb, store) {
|
|
125
|
+
const existing = store.procedures.find(p => p.verb === verb);
|
|
126
|
+
if (existing && existing.triggers.length)
|
|
127
|
+
return existing.triggers;
|
|
128
|
+
const vocab = exports.TIER1_LEARN_TRIGGERS.find(p => p.verb === verb);
|
|
129
|
+
if (vocab && vocab.triggers.length)
|
|
130
|
+
return vocab.triggers;
|
|
131
|
+
return [verb];
|
|
132
|
+
}
|
|
133
|
+
// ---------- Open (PURE) -------------------------------------------------------
|
|
134
|
+
function openWatch(verb, phase, openedAt) {
|
|
135
|
+
return {
|
|
136
|
+
verb,
|
|
137
|
+
phase,
|
|
138
|
+
openedAt: openedAt || new Date().toISOString(),
|
|
139
|
+
turnCount: 0,
|
|
140
|
+
observations: [],
|
|
141
|
+
lastLiveTurn: 0,
|
|
142
|
+
offStreak: 0,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function oneLine(s) {
|
|
146
|
+
return (s || '').replace(/\s+/g, ' ').trim();
|
|
147
|
+
}
|
|
148
|
+
/** Build one compact TurnDigest: user-prompt head ≤300 chars, Bash commands
|
|
149
|
+
* (+ a short output-tail exit hint) from toolCalls, files touched, turn
|
|
150
|
+
* status — ≤1.5K serialized total. PURE — unit-tested. */
|
|
151
|
+
function buildTurnDigest(turn, turnNumber) {
|
|
152
|
+
const commands = [];
|
|
153
|
+
for (const tc of turn.toolCalls) {
|
|
154
|
+
if (tc.name !== 'Bash' || !tc.brief)
|
|
155
|
+
continue;
|
|
156
|
+
if (commands.length >= DIGEST_MAX_COMMANDS)
|
|
157
|
+
break;
|
|
158
|
+
let cmd = oneLine(tc.brief).slice(0, DIGEST_COMMAND_CAP);
|
|
159
|
+
if (tc._output) {
|
|
160
|
+
const lines = tc._output.split('\n').map(l => l.trim()).filter(Boolean);
|
|
161
|
+
const tail = lines.length ? lines[lines.length - 1].slice(0, DIGEST_EXIT_HINT_CAP) : '';
|
|
162
|
+
if (tail)
|
|
163
|
+
cmd += ` ⇒ ${tail}`;
|
|
164
|
+
}
|
|
165
|
+
commands.push(cmd);
|
|
166
|
+
}
|
|
167
|
+
const digest = {
|
|
168
|
+
turn: turnNumber,
|
|
169
|
+
user: oneLine(turn.userPrompt).slice(0, exports.DIGEST_USER_CAP),
|
|
170
|
+
commands,
|
|
171
|
+
files: turn.filesTouched.slice(0, DIGEST_MAX_FILES).map(f => f.slice(0, 160)),
|
|
172
|
+
status: turn.status || 'completed',
|
|
173
|
+
};
|
|
174
|
+
// Total-size guard: trim breadth (files, then commands, then the user head)
|
|
175
|
+
// until the serialized digest fits the cap.
|
|
176
|
+
while (JSON.stringify(digest).length > exports.DIGEST_TOTAL_CAP) {
|
|
177
|
+
if (digest.files.length > 2)
|
|
178
|
+
digest.files = digest.files.slice(0, digest.files.length - 1);
|
|
179
|
+
else if (digest.commands.length > 1)
|
|
180
|
+
digest.commands = digest.commands.slice(0, digest.commands.length - 1);
|
|
181
|
+
else if (digest.user.length > 100)
|
|
182
|
+
digest.user = digest.user.slice(0, digest.user.length - 50);
|
|
183
|
+
else
|
|
184
|
+
break; // structurally minimal — accept
|
|
185
|
+
}
|
|
186
|
+
return digest;
|
|
187
|
+
}
|
|
188
|
+
/** Would observing one more turn cross the watchdog? The caller checks this
|
|
189
|
+
* BEFORE the judge call so the aborting turn costs nothing. PURE. */
|
|
190
|
+
function willExceedWatchdog(watch) {
|
|
191
|
+
return watch.turnCount >= exports.WATCH_MAX_TURNS;
|
|
192
|
+
}
|
|
193
|
+
/** Advance the watch by one observed turn. Appends the digest, then applies
|
|
194
|
+
* the continuity verdict per §Endpoint detection:
|
|
195
|
+
* live=true → stamp lastLiveTurn, reset offStreak
|
|
196
|
+
* live=false → offStreak++
|
|
197
|
+
* live=null → judge unreachable: offStreak UNTOUCHED (the watch extends;
|
|
198
|
+
* the watchdog is the backstop)
|
|
199
|
+
* Dispositions: 'watchdog' when the turn count exceeds the cap (defensive —
|
|
200
|
+
* callers should pre-check willExceedWatchdog), 'close' at
|
|
201
|
+
* offStreak ≥ WATCH_OFF_STREAK_CLOSE, else 'continue'. PURE — unit-tested. */
|
|
202
|
+
function advanceWatch(watch, digest, live) {
|
|
203
|
+
const next = {
|
|
204
|
+
...watch,
|
|
205
|
+
turnCount: watch.turnCount + 1,
|
|
206
|
+
observations: [...watch.observations, digest],
|
|
207
|
+
};
|
|
208
|
+
if (next.turnCount > exports.WATCH_MAX_TURNS) {
|
|
209
|
+
return { watch: next, disposition: 'watchdog' };
|
|
210
|
+
}
|
|
211
|
+
if (live === true) {
|
|
212
|
+
next.lastLiveTurn = next.turnCount;
|
|
213
|
+
next.offStreak = 0;
|
|
214
|
+
}
|
|
215
|
+
else if (live === false) {
|
|
216
|
+
next.offStreak = watch.offStreak + 1;
|
|
217
|
+
} // live === null → untouched
|
|
218
|
+
if (next.offStreak >= exports.WATCH_OFF_STREAK_CLOSE) {
|
|
219
|
+
return { watch: next, disposition: 'close' };
|
|
220
|
+
}
|
|
221
|
+
return { watch: next, disposition: 'continue' };
|
|
222
|
+
}
|
|
223
|
+
/** The live span — the endpoint stamps at the last LIVE turn, so trailing
|
|
224
|
+
* not-live turns never enter the artifact. Span = observations[0 ..
|
|
225
|
+
* lastLiveTurn]. Empty when the watch was never live (nothing to distill —
|
|
226
|
+
* never register garbage). PURE. */
|
|
227
|
+
function liveSpan(watch) {
|
|
228
|
+
return watch.observations.slice(0, watch.lastLiveTurn);
|
|
229
|
+
}
|
|
230
|
+
/** Apply the spec's transition table (component 6) to the procedure store:
|
|
231
|
+
*
|
|
232
|
+
* LEARN | any (span ended) | status=learned, steps/endpoint/caveats from distill
|
|
233
|
+
* REPLAY | clean | status=confirmed (steps untouched)
|
|
234
|
+
* REPLAY | floundered | re-learn in place: distill the failed span → learned
|
|
235
|
+
* any | distill unreadable / judge down | no write; watch discarded
|
|
236
|
+
*
|
|
237
|
+
* Each row gates on the fields it actually consumes: LEARN and
|
|
238
|
+
* REPLAY-floundered need the distilled fields; REPLAY-clean needs the
|
|
239
|
+
* outcome. A missing required field = the unreadable/judge-down row → no
|
|
240
|
+
* write. All writes flow through upsertProcedure, whose destructive guard
|
|
241
|
+
* already refuses to overwrite a `push` seed (never weakened here).
|
|
242
|
+
* PURE — unit-tested. Returns the (possibly unchanged) store + what happened. */
|
|
243
|
+
function applyDistillTransition(store, watch, outcome, distilled, fallbackTriggers) {
|
|
244
|
+
const existing = store.procedures.find(p => p.verb === watch.verb);
|
|
245
|
+
if (watch.phase === 'LEARN') {
|
|
246
|
+
if (!distilled)
|
|
247
|
+
return { store, action: 'discarded' };
|
|
248
|
+
const proc = {
|
|
249
|
+
verb: watch.verb,
|
|
250
|
+
triggers: existing?.triggers?.length ? existing.triggers : fallbackTriggers,
|
|
251
|
+
steps: distilled.steps,
|
|
252
|
+
endpoint: distilled.endpoint,
|
|
253
|
+
caveats: distilled.caveats,
|
|
254
|
+
status: 'learned',
|
|
255
|
+
destructive: existing?.destructive,
|
|
256
|
+
};
|
|
257
|
+
return { store: (0, procedure_1.upsertProcedure)(store, proc), action: 'learned' };
|
|
258
|
+
}
|
|
259
|
+
// REPLAY
|
|
260
|
+
if (outcome === 'clean') {
|
|
261
|
+
if (!existing)
|
|
262
|
+
return { store, action: 'discarded' };
|
|
263
|
+
return {
|
|
264
|
+
store: (0, procedure_1.upsertProcedure)(store, { ...existing, status: 'confirmed' }),
|
|
265
|
+
action: 'confirmed',
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (outcome === 'floundered') {
|
|
269
|
+
// The failed span already contains the new winning path — a stale REPLAY
|
|
270
|
+
// span IS the next LEARN span; no extra failure needed.
|
|
271
|
+
if (!distilled)
|
|
272
|
+
return { store, action: 'discarded' };
|
|
273
|
+
const proc = {
|
|
274
|
+
verb: watch.verb,
|
|
275
|
+
triggers: existing?.triggers?.length ? existing.triggers : fallbackTriggers,
|
|
276
|
+
steps: distilled.steps,
|
|
277
|
+
endpoint: distilled.endpoint,
|
|
278
|
+
caveats: distilled.caveats,
|
|
279
|
+
status: 'learned',
|
|
280
|
+
destructive: existing?.destructive,
|
|
281
|
+
};
|
|
282
|
+
return { store: (0, procedure_1.upsertProcedure)(store, proc), action: 'relearned' };
|
|
283
|
+
}
|
|
284
|
+
// outcome null → judge down → no write.
|
|
285
|
+
return { store, action: 'discarded' };
|
|
286
|
+
}
|
|
287
|
+
// ---------- Watch file I/O (beside the procedure store) -----------------------
|
|
288
|
+
function stateDir() {
|
|
289
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
290
|
+
return path.join(home, '.greprag', 'state');
|
|
291
|
+
}
|
|
292
|
+
function procedureWatchPath(projectId) {
|
|
293
|
+
return path.join(stateDir(), `procedure-watch-${projectId}.json`);
|
|
294
|
+
}
|
|
295
|
+
/** The Stop-hook observer's zero-cost gate: no file → no watch → no work. */
|
|
296
|
+
function hasProcedureWatch(projectId) {
|
|
297
|
+
try {
|
|
298
|
+
return fs.existsSync(procedureWatchPath(projectId));
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/** Read the active watch. Null on any miss/corruption — fail-open; the caller
|
|
305
|
+
* clears the file so a corrupt watch can't wedge the observer. */
|
|
306
|
+
function readProcedureWatch(projectId) {
|
|
307
|
+
try {
|
|
308
|
+
const raw = fs.readFileSync(procedureWatchPath(projectId), 'utf-8');
|
|
309
|
+
const w = JSON.parse(raw);
|
|
310
|
+
if (w && typeof w.verb === 'string' && w.verb
|
|
311
|
+
&& (w.phase === 'LEARN' || w.phase === 'REPLAY')
|
|
312
|
+
&& Array.isArray(w.observations)) {
|
|
313
|
+
return {
|
|
314
|
+
verb: w.verb,
|
|
315
|
+
phase: w.phase,
|
|
316
|
+
openedAt: typeof w.openedAt === 'string' ? w.openedAt : '',
|
|
317
|
+
turnCount: typeof w.turnCount === 'number' ? w.turnCount : w.observations.length,
|
|
318
|
+
observations: w.observations,
|
|
319
|
+
lastLiveTurn: typeof w.lastLiveTurn === 'number' ? w.lastLiveTurn : 0,
|
|
320
|
+
offStreak: typeof w.offStreak === 'number' ? w.offStreak : 0,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch { /* missing / unreadable → null */ }
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
function writeProcedureWatch(projectId, watch) {
|
|
328
|
+
const file = procedureWatchPath(projectId);
|
|
329
|
+
const dir = path.dirname(file);
|
|
330
|
+
if (!fs.existsSync(dir))
|
|
331
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
332
|
+
fs.writeFileSync(file, JSON.stringify(watch, null, 2) + '\n');
|
|
333
|
+
}
|
|
334
|
+
function clearProcedureWatch(projectId) {
|
|
335
|
+
try {
|
|
336
|
+
fs.unlinkSync(procedureWatchPath(projectId));
|
|
337
|
+
}
|
|
338
|
+
catch { /* already gone */ }
|
|
339
|
+
}
|
|
340
|
+
/** Open a watch iff none is active (ONE active watch at a time, v1 — an
|
|
341
|
+
* in-flight watch is never replaced by a new trigger). Returns whether a
|
|
342
|
+
* watch was opened. */
|
|
343
|
+
function openWatchIfIdle(projectId, verb, phase) {
|
|
344
|
+
if (hasProcedureWatch(projectId))
|
|
345
|
+
return false;
|
|
346
|
+
writeProcedureWatch(projectId, openWatch(verb, phase));
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
//# sourceMappingURL=procedure-watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procedure-watch.js","sourceRoot":"","sources":["../src/procedure-watch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Skill-mirror client — the Tier-3 mirror's upload half
|
|
2
|
+
* (docs/load-system.md §Tier 3). Runs inside the Stop hook: a skill-load turn
|
|
3
|
+
* triggers `maybeMirrorSkill`, which content-hashes the skill's markdown and
|
|
4
|
+
* uploads ONLY on change. Follower semantics — files canonical, the mirror
|
|
5
|
+
* shadows "last used state." Best-effort end to end: a mirror failure never
|
|
6
|
+
* touches the turn.
|
|
7
|
+
*
|
|
8
|
+
* Local freshness state (`~/.greprag/state/skill-mirror.json`) remembers the
|
|
9
|
+
* last-mirrored hash per skill so an unchanged skill costs one hash pass and
|
|
10
|
+
* ZERO network. Markdown only, v1 — SKILL.md + docs/**\/*.md; script-bearing
|
|
11
|
+
* assets stay file-bound (see the spec's named gaps). */
|
|
12
|
+
export interface SkillFile {
|
|
13
|
+
path: string;
|
|
14
|
+
content: string;
|
|
15
|
+
}
|
|
16
|
+
/** Collect the skill's markdown: SKILL.md (required) + docs/**\/*.md, capped.
|
|
17
|
+
* Returns null when the dir has no SKILL.md. Never throws. */
|
|
18
|
+
export declare function collectSkillFiles(skillDir: string): SkillFile[] | null;
|
|
19
|
+
/** Deterministic content hash over path+content pairs. PURE — unit-tested. */
|
|
20
|
+
export declare function computeMirrorHash(files: SkillFile[]): string;
|
|
21
|
+
interface MirrorState {
|
|
22
|
+
skills: Record<string, {
|
|
23
|
+
hash: string;
|
|
24
|
+
mirroredAt: string;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export declare function readMirrorState(): MirrorState;
|
|
28
|
+
export declare function writeMirrorState(state: MirrorState): void;
|
|
29
|
+
export type MirrorOutcome = 'mirrored' | 'fresh' | 'skipped';
|
|
30
|
+
/** Mirror one skill if its content changed since the last mirror. Never throws. */
|
|
31
|
+
export declare function maybeMirrorSkill(params: {
|
|
32
|
+
skill: string;
|
|
33
|
+
cwd: string;
|
|
34
|
+
apiUrl: string;
|
|
35
|
+
apiKey: string;
|
|
36
|
+
}): Promise<MirrorOutcome>;
|
|
37
|
+
export {};
|