@tekyzinc/gsd-t 4.6.11 → 4.7.11
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/CHANGELOG.md +28 -0
- package/README.md +1 -1
- package/bin/gsd-t-architectural-trigger.cjs +523 -0
- package/bin/gsd-t-archive-domains.cjs +136 -0
- package/bin/gsd-t-loop-ledger.cjs +519 -0
- package/bin/gsd-t-research-gate.cjs +439 -0
- package/bin/gsd-t.js +82 -1
- package/commands/gsd-t-complete-milestone.md +15 -2
- package/commands/gsd-t-help.md +25 -0
- package/docs/requirements.md +17 -0
- package/package.json +1 -1
- package/templates/CLAUDE-global.md +35 -16
- package/templates/prompts/blind-adversary-subagent.md +101 -0
- package/templates/prompts/research-subagent.md +127 -0
- package/templates/prompts/stated-claims-snippet.md +89 -0
- package/templates/test-helpers/launch-extension.ts +81 -0
- package/templates/workflows/gsd-t-debug.workflow.js +312 -0
- package/templates/workflows/gsd-t-execute.workflow.js +366 -1
- package/templates/workflows/gsd-t-phase.workflow.js +482 -5
- package/templates/workflows/gsd-t-quick.workflow.js +272 -2
- package/templates/workflows/gsd-t-verify.workflow.js +261 -1
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* gsd-t-research-gate.cjs — M90 D3 (v1.4.0 — time-anchored override + premise-corrected vendor list)
|
|
5
|
+
*
|
|
6
|
+
* Deterministic internal-vs-external-vs-AMBIGUOUS gap classifier. Given a GUESSED
|
|
7
|
+
* CLAIM (a claim the agent tagged GUESSED in its Stated-Claims list per §6.5),
|
|
8
|
+
* returns the house-style JSON envelope:
|
|
9
|
+
*
|
|
10
|
+
* { ok:true, gap, class:"internal"|"external"|"ambiguous", route:"grep"|"web"|"judge", reason }
|
|
11
|
+
*
|
|
12
|
+
* On bad input:
|
|
13
|
+
* { ok:false, error:"<reason>" }
|
|
14
|
+
*
|
|
15
|
+
* --- THE DOCTRINE APPLIED TO THE CLASSIFIER (v1.3.0 premise correction, M90 additions) ---
|
|
16
|
+
*
|
|
17
|
+
* M89's own rule is: never act on belief; if a claim is not grounded in definitive
|
|
18
|
+
* knowledge or research evidence, RESEARCH it. The previous classifier (~745 LOC of
|
|
19
|
+
* hand-fit regexes) violated that rule against itself: it tried to SEMANTICALLY decide
|
|
20
|
+
* "is this an external claim?" via pattern-guessing. So this classifier is a MECHANICAL
|
|
21
|
+
* STRING-FACT FILTER, NOT a semantic oracle.
|
|
22
|
+
*
|
|
23
|
+
* M90 premise correction (D3-T0 baseline, 2026-06-22):
|
|
24
|
+
* The partition asserted the vendor list caused "silent-miss" routing (an absent vendor →
|
|
25
|
+
* silently-internal). Verified FALSE on disk: an absent vendor with no internal signal falls
|
|
26
|
+
* through to `ambiguous→judge` ("never guess-internal"). The vendor list ONLY UPGRADES a
|
|
27
|
+
* vendor+API match to high-confidence `external→web`; its absence never routes internal.
|
|
28
|
+
* M89 already removed ALL "wins-outright→internal" overrides (auto-research-contract v1.3.3).
|
|
29
|
+
* Therefore: the vendor list is KEPT (deleting it would DOWNGRADE Stripe/Chrome/Plaid/Twilio
|
|
30
|
+
* et al. from `external→web` to `ambiguous→judge` — a pure regression). Changed or tightened
|
|
31
|
+
* ONLY if a concrete misroute defect is proven by the T0 baseline; never deleted on the
|
|
32
|
+
* falsified silent-miss premise. (Empirical: 10/10 unseen never-seen vendors → `ambiguous→judge`;
|
|
33
|
+
* 0/10 silently-internal — see D3-T0 known-answer section in the corpus test.)
|
|
34
|
+
*
|
|
35
|
+
* --- M90 D3 ADDITION: R-FACT-3 TIME-ANCHORED PROTOCOL OVERRIDE ---
|
|
36
|
+
*
|
|
37
|
+
* A confidence gate cannot catch a stale-but-confident fact (intrinsic self-knowledge signals
|
|
38
|
+
* collapse to chance on the temporal axis — CoVe, arXiv:2309.11495; Self-RAG, arXiv:2310.11511;
|
|
39
|
+
* temporal-collapse survey, arXiv:2510.19172v1). Therefore:
|
|
40
|
+
*
|
|
41
|
+
* A claim that is FAST-MOVING (a versioned library, API, protocol spec, or "current/latest
|
|
42
|
+
* best practice") → route to research REGARDLESS of any confidence signal.
|
|
43
|
+
*
|
|
44
|
+
* This is a deterministic string-fact check: a small set of temporal-signal phrases (e.g.
|
|
45
|
+
* "current best practice", "latest version", "current version", "stable release" — CLOSED
|
|
46
|
+
* PHRASES, not an open semantic category) fires BEFORE the vendor/internal signal tests and
|
|
47
|
+
* short-circuits to `class:"external", route:"web"`. The rationale: these phrases are string
|
|
48
|
+
* facts that the claim is fast-moving; the correct behavior under M89's own doctrine is to
|
|
49
|
+
* always verify, never trust a cached belief about what's "latest" or "current".
|
|
50
|
+
*
|
|
51
|
+
* --- THE THREE RESULT CLASSES ---
|
|
52
|
+
*
|
|
53
|
+
* - TIME-ANCHORED (checked FIRST, deterministic): a fast-moving-signal phrase → external/web
|
|
54
|
+
* always. Bypasses the vendor/internal test (temporal collapse makes confidence
|
|
55
|
+
* unreliable).
|
|
56
|
+
* - `internal` ONLY on a STRING-FACT internal signal (a concrete repo path/file
|
|
57
|
+
* shape, a real gsd-t-* tool name, or an explicit this-repo anchor
|
|
58
|
+
* phrase). These are string facts about THIS repo, not beliefs.
|
|
59
|
+
* - `external` ONLY on an UNAMBIGUOUS string-fact external signal: a recognized
|
|
60
|
+
* vendor/product proper noun (long, multi-char, non-homograph) that
|
|
61
|
+
* CO-OCCURS with an API/HTTP/protocol term. Kept SHORT and unambiguous;
|
|
62
|
+
* when in doubt a token is left OUT (→ ambiguous, which is safe).
|
|
63
|
+
* - `ambiguous` EVERYTHING ELSE. If placing a claim requires JUDGMENT rather than a
|
|
64
|
+
* string fact, it is NOT regex's call — it goes to the LLM judge, and if
|
|
65
|
+
* the LLM is not confident it is RESEARCHED (uncertain→verify). The
|
|
66
|
+
* ambiguous→LLM→uncertain→research routing lives in the WIRING (the 4
|
|
67
|
+
* workflows), not here — D1 stays a pure calculator.
|
|
68
|
+
*
|
|
69
|
+
* Hard rules:
|
|
70
|
+
* - Deterministic: identical claim text → byte-identical envelope
|
|
71
|
+
* - Zero new runtime deps (Node built-ins only), sync APIs
|
|
72
|
+
* - Never throws on string input (returns {ok:false,error} instead)
|
|
73
|
+
* - route is derived from class: internal→grep, external→web, ambiguous→judge
|
|
74
|
+
* - Bad input (empty/whitespace/non-string) → {ok:false,error} + non-zero CLI exit
|
|
75
|
+
*
|
|
76
|
+
* Contract: .gsd-t/contracts/auto-research-contract.md §1/§1.1 v1.3.3 STABLE
|
|
77
|
+
* .gsd-t/contracts/m90-doctrine-mechanisms-contract.md §1 v1.0.0
|
|
78
|
+
*/
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// R-FACT-3 — Time-anchored protocol override (M90 D3 addition)
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
//
|
|
84
|
+
// A CLOSED set of temporal-signal phrases that indicate a claim is fast-moving
|
|
85
|
+
// (a versioned library, API spec, protocol, or "current/latest best practice").
|
|
86
|
+
// When matched, the claim bypasses ALL confidence gates and routes to research
|
|
87
|
+
// IMMEDIATELY (`class:"external", route:"web"`). These are STRING FACTS that
|
|
88
|
+
// the claim is temporal, not semantic judgments. The set is intentionally short
|
|
89
|
+
// and conservative — false positives (non-temporal claims matching) are safe
|
|
90
|
+
// (they get researched rather than cached); false negatives reach the ambiguous
|
|
91
|
+
// path and are judged. Phrases are checked BEFORE vendor/internal signals.
|
|
92
|
+
//
|
|
93
|
+
// Source: CoVe (arXiv:2309.11495) — intrinsic self-knowledge collapses on the
|
|
94
|
+
// temporal axis; a confidence gate cannot catch a stale-but-confident fact.
|
|
95
|
+
// Self-RAG (arXiv:2310.11511) — temporal freshness is a distinct retrieval axis.
|
|
96
|
+
// Temporal-collapse survey (arXiv:2510.19172v1).
|
|
97
|
+
//
|
|
98
|
+
// SC-NO-FINITE-LIST compliance: the INTERNAL decision still enumerates no OPEN
|
|
99
|
+
// category (internal requires a positive own-repo signal, never the mere absence
|
|
100
|
+
// of a temporal phrase). The temporal phrases are a closed set for the EXTERNAL
|
|
101
|
+
// decision (routing to web/research), not for the internal decision.
|
|
102
|
+
|
|
103
|
+
/** Closed set of temporal-signal phrases → always route to research (R-FACT-3).
|
|
104
|
+
*
|
|
105
|
+
* Design constraint: phrases must be SPECIFIC enough to avoid false-positives on
|
|
106
|
+
* internal-convention questions (e.g. bare "best practice" alone would fire on
|
|
107
|
+
* "what is our best practice for naming domains?" — an internal doc question).
|
|
108
|
+
* Each phrase here is qualified by a temporal/freshness adjective ("current",
|
|
109
|
+
* "latest", "recommended", "stable", "deprecated") that makes the fast-moving
|
|
110
|
+
* nature explicit. Bare "best practice" or "version" alone are NOT in this list.
|
|
111
|
+
*/
|
|
112
|
+
const TEMPORAL_SIGNAL_PHRASES = [
|
|
113
|
+
// "current/latest" best-practice or standard (qualified with temporal adjective)
|
|
114
|
+
"current best practice", "current best-practice",
|
|
115
|
+
"latest best practice", "latest best-practice",
|
|
116
|
+
// "current" / "latest" version signals
|
|
117
|
+
"current version", "latest version", "latest stable",
|
|
118
|
+
"current release", "latest release", "stable release",
|
|
119
|
+
"newest version", "most recent version",
|
|
120
|
+
// upgrade / deprecation / migration signals (fast-moving)
|
|
121
|
+
"upgrade to", "migrate to", "migration guide",
|
|
122
|
+
"deprecated in", "is deprecated", "was deprecated",
|
|
123
|
+
// "recommended" signals (recommendations change — qualified to avoid false positives)
|
|
124
|
+
"recommended approach", "recommended way", "recommended practice",
|
|
125
|
+
"recommended version", "currently recommended",
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Word-boundary token match (kills the substring-match anti-pattern)
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
//
|
|
132
|
+
// `text.includes(token)` matches INSIDE words ("our" hits "four", "rest" hits "the
|
|
133
|
+
// rest of"). The correct test is a WORD-BOUNDARY match. A token may contain spaces,
|
|
134
|
+
// dots, slashes and hyphens (e.g. "rest api", "node.js", "/v1/"); we anchor on \b only
|
|
135
|
+
// where the token edge is a word char and fall back to a plain substring test for edges
|
|
136
|
+
// that are non-word (a \b before "/" is meaningless).
|
|
137
|
+
|
|
138
|
+
/** Escape a string for use as a literal inside a RegExp. */
|
|
139
|
+
function escapeRegExp(s) {
|
|
140
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Word-boundary-aware token match against an already-lowercased haystack.
|
|
145
|
+
* @param {string} lowerText - the lowercased claim text
|
|
146
|
+
* @param {string} token - a lowercased token (may contain spaces/dots/slashes/hyphens)
|
|
147
|
+
* @returns {boolean}
|
|
148
|
+
*/
|
|
149
|
+
function boundaryMatch(lowerText, token) {
|
|
150
|
+
const t = token.trim();
|
|
151
|
+
if (t.length === 0) return false;
|
|
152
|
+
const left = /\w/.test(t[0]) ? "\\b" : "";
|
|
153
|
+
const right = /\w/.test(t[t.length - 1]) ? "\\b" : "";
|
|
154
|
+
return new RegExp(left + escapeRegExp(t) + right).test(lowerText);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// INTERNAL string-facts (this-repo anchors + repo path/file/tool shapes)
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
//
|
|
161
|
+
// These are STRING FACTS about this repo, not semantic guesses. An explicit repo
|
|
162
|
+
// anchor phrase, or a concrete repo-relative path / file-naming shape / real gsd-t-*
|
|
163
|
+
// tool name, is a verifiable string-level signal that the claim is about THIS repo's
|
|
164
|
+
// own code. Anything subtler (a bare camelCase symbol that COULD be external, a
|
|
165
|
+
// question phrasing) is NOT a string fact → it falls through to ambiguous.
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* This-repo anchor phrases. An anchor signals internal ONLY when NO strong external signal
|
|
169
|
+
* (a real vendor proper-noun + API term) co-occurs (see the final decision rule in
|
|
170
|
+
* `classify()`). An anchor NEVER overrides a strong external signal — a generic English
|
|
171
|
+
* phrase like "exit code" / "who owns" / "this file" can appear in a sentence whose real
|
|
172
|
+
* subject is an external vendor ("what exit code does the Stripe API return?"), so it is not
|
|
173
|
+
* a string fact that the claim is internal. (There is no "decisive" vs "broad" split: since
|
|
174
|
+
* NO anchor overrides a strong external signal, the distinction is behaviorally dead — one
|
|
175
|
+
* flat list. Interrogative words are deliberately ABSENT — neutral, the natural way to phrase
|
|
176
|
+
* an external research question.)
|
|
177
|
+
*/
|
|
178
|
+
const INTERNAL_ANCHORS = [
|
|
179
|
+
"this repo", "this repo's", "repo's",
|
|
180
|
+
"our repo", "the repo",
|
|
181
|
+
"this codebase", "our codebase",
|
|
182
|
+
"in this project", "our project",
|
|
183
|
+
"this module", "our module",
|
|
184
|
+
"this file", "our file",
|
|
185
|
+
"the existing", "our existing",
|
|
186
|
+
"in the codebase",
|
|
187
|
+
"our", "our internal",
|
|
188
|
+
// ownership / file-system phrasings about this repo's structure
|
|
189
|
+
"which domain owns", "which file owns", "who owns",
|
|
190
|
+
// exit code / return value of a module
|
|
191
|
+
"exit code",
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Internal file / path / tool-name SHAPE patterns. STRUCTURAL string facts (file
|
|
196
|
+
* extensions, repo-relative path separators, GSD-T's naming convention) — NOT an
|
|
197
|
+
* enumerated list of specific held-out symbols (a guard test forbids re-hardcoding
|
|
198
|
+
* those). A claim referencing one of these references this repo's own files.
|
|
199
|
+
*/
|
|
200
|
+
const INTERNAL_FILE_PATTERNS = [
|
|
201
|
+
// GSD-T module / file naming conventions (shape, not a specific filename)
|
|
202
|
+
"gsd-t-", ".workflow.js", ".test.js", "gsd-t.js",
|
|
203
|
+
// contract / domain / scope file shapes
|
|
204
|
+
"-contract.md", "scope.md", "tasks.md", "constraints.md",
|
|
205
|
+
"progress.md", "architecture.md", "workflows.md",
|
|
206
|
+
// repo-relative path separators
|
|
207
|
+
"bin/", "test/", "templates/", "commands/", ".gsd-t/",
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
// EXTERNAL string-facts (unambiguous vendor proper-noun + API/protocol term)
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
//
|
|
214
|
+
// SHORT and unambiguous by design (the directive). Only LONG, multi-char, NON-homograph
|
|
215
|
+
// vendor/product proper nouns — never single-word English homographs ("go"/"square"/
|
|
216
|
+
// "edge"/"rest"/"swift"/"amazon"…), which are left OUT so a benign internal sentence is
|
|
217
|
+
// never misrouted external. `external` fires ONLY when such a proper noun CO-OCCURS with
|
|
218
|
+
// an API/HTTP/protocol term: that conjunction is an unambiguous string fact that the
|
|
219
|
+
// claim is about an external system's contract. A proper noun ALONE, or a term alone, is
|
|
220
|
+
// NOT enough — it falls through to ambiguous (the LLM's call), which is safe.
|
|
221
|
+
|
|
222
|
+
/** Unambiguous third-party vendor / product proper nouns (string facts, no homographs). */
|
|
223
|
+
const EXTERNAL_VENDOR_NOUNS = [
|
|
224
|
+
// payment processors
|
|
225
|
+
"paypal", "stripe", "braintree", "adyen", "klarna", "plaid",
|
|
226
|
+
// cloud platforms / CDN / infra
|
|
227
|
+
"azure", "google cloud", "gcp", "cloudflare", "cloudfront",
|
|
228
|
+
"fastly", "akamai", "vercel", "netlify",
|
|
229
|
+
// auth providers
|
|
230
|
+
"auth0", "okta", "cognito",
|
|
231
|
+
// databases / data
|
|
232
|
+
"mongodb", "postgresql", "elasticsearch", "dynamodb",
|
|
233
|
+
"firestore", "bigquery", "snowflake",
|
|
234
|
+
// messaging / 3rd-party services
|
|
235
|
+
"rabbitmq", "twilio", "sendgrid", "salesforce", "hubspot", "intercom",
|
|
236
|
+
// browsers (multi-char, non-homograph)
|
|
237
|
+
"chrome", "chromium", "firefox", "safari", "webkit",
|
|
238
|
+
// UI frameworks (multi-char names)
|
|
239
|
+
"react", "angular", "svelte",
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
/** API / HTTP / protocol / auth-flow terms (the co-signal that makes a vendor external). */
|
|
243
|
+
const EXTERNAL_API_TERMS = [
|
|
244
|
+
"api", "endpoint", "webhook", "oauth", "openid", "saml", "jwt",
|
|
245
|
+
"rest api", "graphql", "grpc", "openapi", "swagger",
|
|
246
|
+
"rate limit", "rate-limit",
|
|
247
|
+
"access token", "refresh token", "bearer token",
|
|
248
|
+
"authorization header", "redirect uri", "redirect url", "callback url",
|
|
249
|
+
"/v1/", "/v2/", "/v3/",
|
|
250
|
+
"http", "https", "header", "request body", "response body",
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// §7 fail-closed cite gate marker format (R-FACT-4, preserved from M89)
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
//
|
|
257
|
+
// When an external claim is classified, the wiring writes a machine-readable marker
|
|
258
|
+
// into the artifact at classify time:
|
|
259
|
+
// <!-- auto-research-claim: class=external key=<claim-key> status=uncited -->
|
|
260
|
+
// The verify gate (gsd-t-verify.workflow.js §7) FAILs if this marker is still
|
|
261
|
+
// status=uncited after the research stage. The marker is flipped to status=cited
|
|
262
|
+
// when the Verified-Facts block lands. This module's role: produce the `class`,
|
|
263
|
+
// `route`, and `reason` fields that trigger marker creation in the wiring. The
|
|
264
|
+
// marker write is the WIRING's responsibility; this classifier is a pure calculator.
|
|
265
|
+
// R-FACT-4: this marker mechanism is PRESERVED unchanged from the M89 baseline.
|
|
266
|
+
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// Classification — a mechanical string-fact filter (no semantic judgment)
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Classify a guessed claim as internal / external / ambiguous by STRING FACTS only.
|
|
273
|
+
*
|
|
274
|
+
* Decision order (deterministic, checked in sequence):
|
|
275
|
+
* 1. Bad-input guard (SC1): empty/whitespace/non-string → {ok:false}, never silent.
|
|
276
|
+
* 2. TIME-ANCHORED OVERRIDE (R-FACT-3): a fast-moving signal phrase (current/latest
|
|
277
|
+
* best practice, version signals, deprecation, upgrade) → external/web ALWAYS,
|
|
278
|
+
* bypassing all other tests. Temporal-collapse makes confidence unreliable on this
|
|
279
|
+
* class of claim; the safe rule is always-research.
|
|
280
|
+
* 3. STRONG EXTERNAL: an unambiguous vendor proper-noun AND an API/protocol term.
|
|
281
|
+
* + any path/anchor → ambiguous; else → external/web.
|
|
282
|
+
* 4. INTERNAL: concrete repo path/file shape or this-repo anchor, with ZERO strong
|
|
283
|
+
* external signal → internal/grep.
|
|
284
|
+
* 5. AMBIGUOUS: everything else → judge.
|
|
285
|
+
*
|
|
286
|
+
* @param {string} gap - The claim text (a GUESSED claim per §6.5).
|
|
287
|
+
* @returns {{ ok:true, gap:string, class:"internal"|"external"|"ambiguous",
|
|
288
|
+
* route:"grep"|"web"|"judge", reason:string }
|
|
289
|
+
* |{ ok:false, error:string }}
|
|
290
|
+
*/
|
|
291
|
+
function classify(gap) {
|
|
292
|
+
// ── 1. Bad-input guard (SC1) ───────────────────────────────────────────────
|
|
293
|
+
// empty/whitespace/non-string → error envelope, never silent.
|
|
294
|
+
if (typeof gap !== "string") {
|
|
295
|
+
return { ok: false, error: "gap must be a string" };
|
|
296
|
+
}
|
|
297
|
+
const trimmed = gap.trim();
|
|
298
|
+
if (trimmed.length === 0) {
|
|
299
|
+
return { ok: false, error: "gap must not be empty or whitespace-only" };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const lower = trimmed.toLowerCase();
|
|
303
|
+
|
|
304
|
+
// ── 2. TIME-ANCHORED OVERRIDE (R-FACT-3) ─────────────────────────────────
|
|
305
|
+
//
|
|
306
|
+
// A fast-moving claim (best-practice, version, deprecation, recommended approach)
|
|
307
|
+
// ALWAYS routes to research regardless of any confidence signal. Temporal-collapse
|
|
308
|
+
// (CoVe/Self-RAG) makes a cached "latest" or "best practice" belief unreliable —
|
|
309
|
+
// the correct behavior is always-verify. This is checked BEFORE vendor/internal
|
|
310
|
+
// signals so that e.g. "the current best practice for React hooks api" routes to
|
|
311
|
+
// web even though it also has a vendor (react) + an api term.
|
|
312
|
+
//
|
|
313
|
+
// SC-NO-FINITE-LIST: this is a CLOSED set for the external/temporal decision. The
|
|
314
|
+
// INTERNAL decision still requires a positive own-repo signal (a path or anchor) —
|
|
315
|
+
// the mere ABSENCE of a temporal phrase never routes internal. A claim with a
|
|
316
|
+
// temporal phrase and also an internal path (e.g. "upgrade our gsd-t-phase.workflow.js
|
|
317
|
+
// to the current best practice") still routes temporal→web (the time-anchored check
|
|
318
|
+
// fires first and the recommendation itself is external regardless of which file
|
|
319
|
+
// will be changed).
|
|
320
|
+
const matchedTemporal = TEMPORAL_SIGNAL_PHRASES.find((p) => boundaryMatch(lower, p));
|
|
321
|
+
if (matchedTemporal) {
|
|
322
|
+
return {
|
|
323
|
+
ok: true, gap: trimmed, class: "external", route: "web",
|
|
324
|
+
reason: `Time-anchored override (R-FACT-3): temporal-signal phrase "${matchedTemporal}" indicates a fast-moving claim (best practice / version / deprecation). Temporal-collapse makes confidence unreliable on this class — always route to research regardless of any other signal (CoVe: arXiv:2309.11495).`,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ── 3. String-fact signals ────────────────────────────────────────────────
|
|
329
|
+
const matchedPath = INTERNAL_FILE_PATTERNS.find((p) => boundaryMatch(lower, p));
|
|
330
|
+
const matchedAnchor = INTERNAL_ANCHORS.find((p) => boundaryMatch(lower, p));
|
|
331
|
+
|
|
332
|
+
// STRONG external string fact: an unambiguous vendor proper-noun AND an API/protocol term.
|
|
333
|
+
// BOTH are required — a bare homograph or a bare URL-looking token cannot make a claim
|
|
334
|
+
// strong-external (the vendor list is multi-char proper nouns only, no homographs).
|
|
335
|
+
//
|
|
336
|
+
// PREMISE-CORRECTED ROLE OF THE VENDOR LIST (M90-D3-T0 baseline):
|
|
337
|
+
// The partition asserted the list caused silent-miss routing. Verified FALSE: an absent
|
|
338
|
+
// vendor with no internal signal falls through to ambiguous→judge (never internal). The
|
|
339
|
+
// list's ACTUAL role: UPGRADE a vendor+API match to high-confidence `external→web`,
|
|
340
|
+
// SKIPPING the judge for known vendors. Deleting it would downgrade Stripe/Chrome/Plaid/
|
|
341
|
+
// Twilio et al. to `ambiguous→judge` — a regression, not a fix. KEPT unchanged.
|
|
342
|
+
const matchedVendor = EXTERNAL_VENDOR_NOUNS.find((v) => boundaryMatch(lower, v));
|
|
343
|
+
const matchedApiTerm = EXTERNAL_API_TERMS.find((t) => boundaryMatch(lower, t));
|
|
344
|
+
const hasStrongExternal = !!matchedVendor && !!matchedApiTerm;
|
|
345
|
+
|
|
346
|
+
// ── FINAL DECISION RULE (the structural resolution of the 7-cycle silent-miss class) ──
|
|
347
|
+
//
|
|
348
|
+
// `internal` is returned ONLY when there is ZERO strong-external signal. The mechanical
|
|
349
|
+
// classifier NEVER overrides an external signal with anything — not a path, not "this
|
|
350
|
+
// repo", not anything. There is no string-fact that is exclusively internal (a single
|
|
351
|
+
// claim can carry BOTH a real repo path AND a real external-API subject at once — e.g.
|
|
352
|
+
// "wire the Stripe OAuth refresh into gsd-t-execute.workflow.js"), so every "wins
|
|
353
|
+
// outright → internal" override re-opens the silent miss. Removing ALL override rules
|
|
354
|
+
// closes the class by construction. The cost is more ambiguous→judge calls — the safe
|
|
355
|
+
// direction (the judge researches when unsure; it never silently guesses internal).
|
|
356
|
+
//
|
|
357
|
+
// hasStrongExternal + (any path/anchor) → ambiguous
|
|
358
|
+
// hasStrongExternal + nothing else → external
|
|
359
|
+
// NO strong external + (path OR anchor) → internal
|
|
360
|
+
// else → ambiguous
|
|
361
|
+
|
|
362
|
+
if (hasStrongExternal) {
|
|
363
|
+
if (matchedPath || matchedAnchor) {
|
|
364
|
+
const co = matchedPath ? `repo path "${matchedPath}"` : `anchor "${matchedAnchor}"`;
|
|
365
|
+
return {
|
|
366
|
+
ok: true, gap: trimmed, class: "ambiguous", route: "judge",
|
|
367
|
+
reason: `Ambiguous: a strong external signal (vendor "${matchedVendor}" + API term "${matchedApiTerm}") co-occurs with an internal ${co} — the mechanical classifier NEVER overrides an external signal (a claim can be about BOTH at once); the LLM judge decides (uncertain → research)`,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
ok: true, gap: trimmed, class: "external", route: "web",
|
|
372
|
+
reason: `External string-fact: vendor proper noun "${matchedVendor}" + API/protocol term "${matchedApiTerm}" (no internal path/anchor) — concerns an external system's contract`,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// No strong external signal at all → a concrete repo path or this-repo anchor is decisive
|
|
377
|
+
// internal (there is nothing external to override).
|
|
378
|
+
if (matchedPath) {
|
|
379
|
+
return {
|
|
380
|
+
ok: true, gap: trimmed, class: "internal", route: "grep",
|
|
381
|
+
reason: `Internal string-fact: repo path/file/tool shape "${matchedPath}" and NO strong external signal — concerns this repo's own files`,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (matchedAnchor) {
|
|
385
|
+
return {
|
|
386
|
+
ok: true, gap: trimmed, class: "internal", route: "grep",
|
|
387
|
+
reason: `Internal string-fact: this-repo anchor "${matchedAnchor}" and NO strong external signal — concerns this repo's own code/structure`,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// EVERYTHING ELSE is AMBIGUOUS — no string fact at all (semantic placement is the LLM's
|
|
392
|
+
// call, not regex's). The wiring routes ambiguous → LLM judge; if the LLM is not
|
|
393
|
+
// confident, the claim is RESEARCHED (uncertain → verify, never guess).
|
|
394
|
+
return {
|
|
395
|
+
ok: true, gap: trimmed, class: "ambiguous", route: "judge",
|
|
396
|
+
reason: "Ambiguous — no strong external signal, no concrete repo path, no this-repo anchor. Semantic placement requires judgment, so the LLM judge decides; if not confident, the claim is researched (uncertain → verify, never guess-internal)",
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ---------------------------------------------------------------------------
|
|
401
|
+
// CLI entry point
|
|
402
|
+
// ---------------------------------------------------------------------------
|
|
403
|
+
|
|
404
|
+
if (require.main === module) {
|
|
405
|
+
// Usage: gsd-t-research-gate classify "<gap>" [--json]
|
|
406
|
+
const args = process.argv.slice(2);
|
|
407
|
+
const filteredArgs = args.filter((a) => a !== "--json");
|
|
408
|
+
|
|
409
|
+
function emit(obj) {
|
|
410
|
+
process.stdout.write(JSON.stringify(obj) + "\n");
|
|
411
|
+
}
|
|
412
|
+
function emitError(msg, exitCode) {
|
|
413
|
+
emit({ ok: false, error: msg });
|
|
414
|
+
process.exit(exitCode);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (filteredArgs[0] !== "classify") {
|
|
418
|
+
emitError(
|
|
419
|
+
`Unknown subcommand "${filteredArgs[0] || ""}". Usage: gsd-t-research-gate classify "<gap>" [--json]`,
|
|
420
|
+
64
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const gap = filteredArgs[1];
|
|
425
|
+
if (gap === undefined || gap === null) {
|
|
426
|
+
emitError("Missing <gap> argument. Usage: gsd-t-research-gate classify \"<gap>\" [--json]", 64);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const result = classify(gap);
|
|
430
|
+
emit(result);
|
|
431
|
+
if (!result.ok) process.exit(1);
|
|
432
|
+
// Success → exit 0
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// ---------------------------------------------------------------------------
|
|
436
|
+
// Module exports
|
|
437
|
+
// ---------------------------------------------------------------------------
|
|
438
|
+
|
|
439
|
+
module.exports = { classify };
|
package/bin/gsd-t.js
CHANGED
|
@@ -1262,6 +1262,14 @@ const GLOBAL_BIN_TOOLS = [
|
|
|
1262
1262
|
"gsd-t-model-tier-policy.cjs",
|
|
1263
1263
|
// M86 — Model-profile config + resolver CLI (standard/pro/premium tier-spend switch).
|
|
1264
1264
|
"gsd-t-model-profile.cjs",
|
|
1265
|
+
// M89 — Auto-research gate classifier (internal vs external claim routing; no LLM call).
|
|
1266
|
+
"gsd-t-research-gate.cjs",
|
|
1267
|
+
// M90 D1 — Architectural-assumption trigger (divergence-sampling + extend-signal; §2).
|
|
1268
|
+
"gsd-t-architectural-trigger.cjs",
|
|
1269
|
+
// M90 D2 — Loop ledger (non-convergence detection + halt directive; §3).
|
|
1270
|
+
"gsd-t-loop-ledger.cjs",
|
|
1271
|
+
// Backlog #40 — deterministic archive+sweep of a completed milestone's domain dirs.
|
|
1272
|
+
"gsd-t-archive-domains.cjs",
|
|
1265
1273
|
];
|
|
1266
1274
|
|
|
1267
1275
|
function installGlobalBinTools() {
|
|
@@ -2563,6 +2571,19 @@ const PROJECT_BIN_TOOLS = [
|
|
|
2563
2571
|
"gsd-t-model-tier-policy.cjs",
|
|
2564
2572
|
// M86 — Model-profile config + resolver CLI (standard/pro/premium tier-spend switch).
|
|
2565
2573
|
"gsd-t-model-profile.cjs",
|
|
2574
|
+
// M89 — Auto-research gate classifier (classify a guessed claim as internal vs external;
|
|
2575
|
+
// propagated to each registered project's bin/ so the workflow runCli fallback resolves
|
|
2576
|
+
// downstream — per [[project_global_bin_propagation_gap]]).
|
|
2577
|
+
"gsd-t-research-gate.cjs",
|
|
2578
|
+
// M90 D1 — Architectural-assumption trigger (divergence-sampling + extend-signal; §2).
|
|
2579
|
+
// Propagated so project-local runCli helpers can invoke it without the global binary.
|
|
2580
|
+
"gsd-t-architectural-trigger.cjs",
|
|
2581
|
+
// M90 D2 — Loop ledger (non-convergence detection + halt directive; §3).
|
|
2582
|
+
// Propagated so project-local runCli helpers (gsd-t-debug.workflow.js) can invoke it.
|
|
2583
|
+
"gsd-t-loop-ledger.cjs",
|
|
2584
|
+
// Backlog #40 — deterministic archive+sweep of a completed milestone's domain dirs
|
|
2585
|
+
// (complete-milestone Step 7). Propagated so complete-milestone can invoke it project-local.
|
|
2586
|
+
"gsd-t-archive-domains.cjs",
|
|
2566
2587
|
];
|
|
2567
2588
|
|
|
2568
2589
|
// Files that older versions of this installer copied into project bin/ but
|
|
@@ -4339,7 +4360,10 @@ function showHelp() {
|
|
|
4339
4360
|
log(` ${CYAN}graph${RESET} Code graph operations (index, status, query)`);
|
|
4340
4361
|
log(` ${CYAN}headless${RESET} Non-interactive execution via claude -p + fast state queries`);
|
|
4341
4362
|
log(` ${CYAN}design-build${RESET} Deterministic design→code pipeline (elements → widgets → pages)`);
|
|
4342
|
-
log(` ${CYAN}
|
|
4363
|
+
log(` ${CYAN}research-gate${RESET} Classify a guessed claim as internal (grep) or external (web-research)`);
|
|
4364
|
+
log(` ${CYAN}architectural-trigger${RESET} Fire the arch-assumption trigger (divergence-sampling | extend-signal)`);
|
|
4365
|
+
log(` ${CYAN}loop-ledger${RESET} Record a debug cycle, read exit-state, or clear re-examination flag`);
|
|
4366
|
+
log(` ${CYAN}help${RESET} Show this help\n`);
|
|
4343
4367
|
log(`${BOLD}Examples:${RESET}`);
|
|
4344
4368
|
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t install`);
|
|
4345
4369
|
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t init my-saas-app`);
|
|
@@ -4692,6 +4716,63 @@ if (require.main === module) {
|
|
|
4692
4716
|
});
|
|
4693
4717
|
process.exit(res.status == null ? 1 : res.status);
|
|
4694
4718
|
}
|
|
4719
|
+
case "research-gate": {
|
|
4720
|
+
// M89 — `gsd-t research-gate classify "<claim>"` thin dispatcher to the
|
|
4721
|
+
// auto-research gate classifier (internal vs external routing; no LLM call).
|
|
4722
|
+
const { spawnSync } = require("child_process");
|
|
4723
|
+
const js = path.join(__dirname, "gsd-t-research-gate.cjs");
|
|
4724
|
+
if (!require("node:fs").existsSync(js)) {
|
|
4725
|
+
error(`gsd-t-research-gate.cjs not found at ${js} — install or build M89-D1 first`);
|
|
4726
|
+
process.exit(1);
|
|
4727
|
+
}
|
|
4728
|
+
const res = spawnSync(process.execPath, [js, ...args.slice(1)], {
|
|
4729
|
+
stdio: "inherit",
|
|
4730
|
+
});
|
|
4731
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
4732
|
+
}
|
|
4733
|
+
case "architectural-trigger": {
|
|
4734
|
+
// M90 D1 — `gsd-t architectural-trigger <subcommand>` thin dispatcher to
|
|
4735
|
+
// the architectural-assumption trigger (divergence-sampling + extend-signal; §2).
|
|
4736
|
+
const { spawnSync } = require("child_process");
|
|
4737
|
+
const js = path.join(__dirname, "gsd-t-architectural-trigger.cjs");
|
|
4738
|
+
if (!require("node:fs").existsSync(js)) {
|
|
4739
|
+
error(`gsd-t-architectural-trigger.cjs not found at ${js} — install or build M90-D1 first`);
|
|
4740
|
+
process.exit(1);
|
|
4741
|
+
}
|
|
4742
|
+
const res = spawnSync(process.execPath, [js, ...args.slice(1)], {
|
|
4743
|
+
stdio: "inherit",
|
|
4744
|
+
});
|
|
4745
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
4746
|
+
}
|
|
4747
|
+
case "loop-ledger": {
|
|
4748
|
+
// M90 D2 — `gsd-t loop-ledger <subcommand>` thin dispatcher to the loop-ledger
|
|
4749
|
+
// (non-convergence detection + halt; §3). Subcommands: append-cycle, read-exit-state,
|
|
4750
|
+
// record-re-examination.
|
|
4751
|
+
const { spawnSync } = require("child_process");
|
|
4752
|
+
const js = path.join(__dirname, "gsd-t-loop-ledger.cjs");
|
|
4753
|
+
if (!require("node:fs").existsSync(js)) {
|
|
4754
|
+
error(`gsd-t-loop-ledger.cjs not found at ${js} — install or build M90-D2 first`);
|
|
4755
|
+
process.exit(1);
|
|
4756
|
+
}
|
|
4757
|
+
const res = spawnSync(process.execPath, [js, ...args.slice(1)], {
|
|
4758
|
+
stdio: "inherit",
|
|
4759
|
+
});
|
|
4760
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
4761
|
+
}
|
|
4762
|
+
case "archive-domains": {
|
|
4763
|
+
// Backlog #40 — `gsd-t archive-domains --domains a,b --archive <dir>` deterministic
|
|
4764
|
+
// archive+sweep of a completed milestone's domain dirs (complete-milestone Step 7).
|
|
4765
|
+
const { spawnSync } = require("child_process");
|
|
4766
|
+
const js = path.join(__dirname, "gsd-t-archive-domains.cjs");
|
|
4767
|
+
if (!require("node:fs").existsSync(js)) {
|
|
4768
|
+
error(`gsd-t-archive-domains.cjs not found at ${js} — install or build backlog #40 first`);
|
|
4769
|
+
process.exit(1);
|
|
4770
|
+
}
|
|
4771
|
+
const res = spawnSync(process.execPath, [js, ...args.slice(1)], {
|
|
4772
|
+
stdio: "inherit",
|
|
4773
|
+
});
|
|
4774
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
4775
|
+
}
|
|
4695
4776
|
case "metrics":
|
|
4696
4777
|
doMetrics(args.slice(1));
|
|
4697
4778
|
break;
|
|
@@ -401,8 +401,21 @@ node scripts/gsd-t-watch-state.js advance --agent-id "$GSD_T_AGENT_ID" --parent-
|
|
|
401
401
|
|
|
402
402
|
Reset `.gsd-t/` for next milestone:
|
|
403
403
|
|
|
404
|
-
1. Archive
|
|
405
|
-
|
|
404
|
+
1. **Archive + sweep the completed milestone's domains — DETERMINISTICALLY (backlog #40, NOT prose).**
|
|
405
|
+
This step was prose-only for ~30 milestones and got skipped, accumulating 77 stale domain dirs
|
|
406
|
+
that polluted the file-disjointness oracle. Run the helper with the EXPLICIT set of THIS
|
|
407
|
+
milestone's domain dirs (the partition's domain set — NOT a blanket wipe; a later still-active
|
|
408
|
+
milestone may legitimately have live domains):
|
|
409
|
+
```bash
|
|
410
|
+
gsd-t archive-domains --domains "{comma-separated domain dir names for THIS milestone}" \
|
|
411
|
+
--archive ".gsd-t/milestones/{name}-{date}" --projectDir .
|
|
412
|
+
```
|
|
413
|
+
(prefer the project-local `bin/gsd-t-archive-domains.cjs` when present). It (a) copies each named
|
|
414
|
+
domain → `<archive>/domains/<name>/`, then (b) removes it from `.gsd-t/domains/`. Idempotent
|
|
415
|
+
(re-running is a no-op), containment-guarded (refuses any name with path separators / dot-segments
|
|
416
|
+
or resolving outside `.gsd-t/domains/`), and fail-closed (a bad name aborts the whole batch — no
|
|
417
|
+
partial sweep). Domains belonging to other (still-active) milestones are left untouched.
|
|
418
|
+
2. (Step 1 already cleared this milestone's domains from `.gsd-t/domains/` — do NOT blanket-wipe the dir.)
|
|
406
419
|
3. Archive current reports → milestone folder
|
|
407
420
|
4. Clear `.gsd-t/impact-report.md`, `.gsd-t/test-coverage.md`
|
|
408
421
|
5. Update `.gsd-t/progress.md`:
|
package/commands/gsd-t-help.md
CHANGED
|
@@ -73,6 +73,9 @@ HEADLESS (CI/CD) CLI
|
|
|
73
73
|
headless query Read project state without LLM (<100ms)
|
|
74
74
|
headless --debug-loop Compaction-proof test-fix-retest loop (fresh sessions)
|
|
75
75
|
parallel Task-level parallel dispatch with mode-aware gating (M44)
|
|
76
|
+
research-gate Classify a guessed claim: internal (grep) / external (web) / ambiguous (LLM judge) [M89]
|
|
77
|
+
architectural-trigger Fire the architectural-assumption trigger (divergence-sampling or extend-existing-code) [M90]
|
|
78
|
+
loop-ledger Record a debug cycle, read exit state, detect non-convergence (premise-re-examination) [M90]
|
|
76
79
|
|
|
77
80
|
BACKLOG Manual
|
|
78
81
|
───────────────────────────────────────────────────────────────────────────────
|
|
@@ -516,6 +519,28 @@ Use these when user asks for help on a specific command:
|
|
|
516
519
|
- **Out of scope**: Session default model (`/model`) — profiles govern WORKFLOW STAGES only.
|
|
517
520
|
- **Contract**: `.gsd-t/contracts/model-profile-config-contract.md` v1.0.0 STABLE.
|
|
518
521
|
|
|
522
|
+
### research-gate (M89)
|
|
523
|
+
- **Summary**: Deterministic CLASSIFY step of the M89 auto-research pipeline. Takes a load-bearing claim the agent tagged `[GUESSED:*]` in its Stated Claims section. The classifier is a MECHANICAL STRING-FACT FILTER (NOT a semantic oracle) returning one of THREE classes: `internal` (grep/Read, route `grep`), `external` (web-research, route `web`), or `ambiguous` (route `judge`). No LLM call in the classifier itself — semantic judgment is deferred to the wiring's LLM judge. An `ambiguous` claim is routed to a small LLM `classify-judge` stage (model: "fable") that decides internal/external/uncertain; **uncertain → research** (never guess-internal). The research `agent()` stage (model: "fable") runs for external guesses. An ENFORCE marker (`<!-- auto-research-claim: ... status=uncited -->`) is written into the artifact (a deterministic `.gsd-t/research/...` fallback if the stage reports no artifact path — fail-closed); the verify gate FAILs if it stays uncited.
|
|
524
|
+
- **Files**: `bin/gsd-t-research-gate.cjs` (D1). Contract: `.gsd-t/contracts/auto-research-contract.md` v1.3.2 STABLE. Stage prompt: `templates/prompts/research-subagent.md`. DETECT snippet: `templates/prompts/stated-claims-snippet.md`. Cite-format test: `test/m89-research-stage-cite-format.test.js`.
|
|
525
|
+
- **Use when**: A workflow stage needs to classify a guessed claim before deciding whether to grep, run a web-research sub-agent, or route to the LLM judge. Called by D3 (upper phases: plan, pre-mortem, partition, discuss, milestone) and D4 (worker phases: execute, debug, quick). NOT called manually — the wiring invokes it per `[GUESSED:*]` entry in the `## Stated Claims` section.
|
|
526
|
+
- **CLI**: `gsd-t research-gate classify "<guessed claim text>"`. Emits `{ok, gap, class, route, reason}` where `class` ∈ {internal, external, ambiguous} and `route` ∈ {grep, web, judge}. Exit 0 on classify · 1 on bad input.
|
|
527
|
+
- **Classification rules (string facts only — the v1.3.2 4-step priority)**: (1) a CONCRETE REPO PATH (`bin/x.cjs` / `*.workflow.js` / `gsd-t-*` / `templates/…`) → `internal` (a path is decisive, beats everything). (2) a STRONG EXTERNAL signal (unambiguous vendor proper-noun + API/protocol term) → at least ambiguous: with ANY anchor phrase present → `ambiguous`; with no anchor → `external`. NO anchor phrase ("this repo" / "exit code" / "who owns") may override a strong external signal — only a concrete path can. (3) an anchor with NO strong external → `internal`. (4) else → `ambiguous`. An `ambiguous` claim → LLM judge → uncertain→research (§1.1/§5.1, owned by D3/D4). The regex never guesses a paraphrase's semantic class.
|
|
528
|
+
- **Contract**: `.gsd-t/contracts/auto-research-contract.md` v1.3.2 STABLE.
|
|
529
|
+
|
|
530
|
+
### architectural-trigger (M90)
|
|
531
|
+
- **Summary**: Deterministic architectural-assumption trigger (M90 §2). Two fire paths: **R-ARCH-1** (divergence-sampling — N fresh-context producer answers → divergence score; competition-arm only, EXPERIMENTAL+MEASURED) and **R-ARCH-2** (protocol-class — any task whose `**Touches**` lists an EXISTING file triggers unconditionally; everywhere feed). Fires produce an instrumentation record in `.gsd-t/metrics/arch-trigger-events.jsonl`; a `provenByAdversaryOnly=true` flag from either path surfaces at the §4 fail-closed verify gate (R-FAIL-2). The module NEVER asserts it works — it emits measurement data only.
|
|
532
|
+
- **Files**: `bin/gsd-t-architectural-trigger.cjs`. Contract: `.gsd-t/contracts/unproven-assumption-doctrine-contract.md` §2. Blind-adversary prompt: `templates/prompts/blind-adversary-subagent.md`. Model: `fable` (M85 RULE-ARCH-TIER).
|
|
533
|
+
- **Use when**: Workflows detect that a task extends existing code (R-ARCH-2 everywhere feed) or that competition producers diverged (R-ARCH-1 competition-arm only). Called automatically by `gsd-t-execute`, `gsd-t-quick`, and `gsd-t-phase` competition arm via inline runCli helper (M81 runtime-native).
|
|
534
|
+
- **CLI**: `gsd-t architectural-trigger trigger '<JSON>'`. JSON shapes: `{"type":"extend-existing-code","context":"...","basis":"..."}` or `{"type":"divergence-sampling","answers":[...],"basis":"..."}`. Emits `{ok, firePath, fired, basis, reason, ...}`. Exit 0 on success · 1 on bad input.
|
|
535
|
+
- **Contract**: `.gsd-t/contracts/unproven-assumption-doctrine-contract.md` §2 v1.0.0 STABLE.
|
|
536
|
+
|
|
537
|
+
### loop-ledger (M90)
|
|
538
|
+
- **Summary**: Cross-process non-convergence detector (M90 §3). Records each debug cycle (symptom-signature + surface + fileClass), computes a stable signature, and detects when the SAME computed signature appears across cycles (non-convergence). After all cycles, `read-exit-state` returns `haltedButNoReExamination=true` when non-convergence is proven — the debug workflow exits with a PREMISE_RE_EXAMINATION directive (option b, re-anchored to the cycle-2 boundary) instead of the generic `needs-human`. A `haltedButNoReExamination=true` state that reaches verify triggers R-FAIL-3 (fail-closed).
|
|
539
|
+
- **Files**: `bin/gsd-t-loop-ledger.cjs`. Contract: `.gsd-t/contracts/unproven-assumption-doctrine-contract.md` §3. Ledger stored at `.gsd-t/ledgers/loop-ledger.jsonl` (per project).
|
|
540
|
+
- **Use when**: Debug workflow calls `append-cycle` after each iteration and `read-exit-state` after the loop. NOT called manually — the debug workflow wires it via inline runCli helper.
|
|
541
|
+
- **CLI**: `gsd-t loop-ledger append-cycle --assertion "<symptom>" --surface "<file>" --fileClass unit --projectDir <dir>`. `gsd-t loop-ledger read-exit-state --projectDir <dir>`. Exit 0 on success.
|
|
542
|
+
- **Contract**: `.gsd-t/contracts/unproven-assumption-doctrine-contract.md` §3 v1.0.0 STABLE.
|
|
543
|
+
|
|
519
544
|
## Unknown Command
|
|
520
545
|
|
|
521
546
|
If user asks for help on unrecognized command:
|