thumbgate 1.37.1 → 1.37.3
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/.agents/skills/article-syndication-pipeline/SKILL.md +37 -0
- package/.agents/skills/ci-gha-buildkite-patterns-not-clone/SKILL.md +72 -0
- package/.agents/skills/cobble-hot-store-compare-not-clone/SKILL.md +80 -0
- package/.agents/skills/colab-compute-honesty-not-clone/SKILL.md +70 -0
- package/.agents/skills/comet-session-logger/SKILL.md +59 -0
- package/.agents/skills/datadog-llm-obs-compare-not-clone/SKILL.md +75 -0
- package/.agents/skills/deeppattern-discipline-honesty-not-clone/SKILL.md +73 -0
- package/.agents/skills/thumbgate-board-loop/SKILL.md +72 -0
- package/.agents/skills/thumbgate-daily-discoveries/SKILL.md +57 -0
- package/.agents/skills/token-shunt-honesty-not-clone/SKILL.md +67 -0
- package/.agents/skills/typesafe-typed-questions-not-clone/SKILL.md +86 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/future-agi/.mcp.json +1 -1
- package/adapters/future-agi/config.toml +1 -1
- package/adapters/future-agi/opencode.json +1 -1
- package/adapters/herdr/herdr-plugin.toml +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +257 -0
- package/config/gate-templates.json +96 -0
- package/package.json +55 -12
- package/public/blog/2026-09-23-datadog-llm-obs-four-practices.html +42 -0
- package/public/index.html +2 -2
- package/public/install.html +7 -7
- package/public/numbers.html +2 -2
- package/scripts/ci-gha-buildkite-patterns.js +393 -0
- package/scripts/cli-schema.js +90 -0
- package/scripts/cobble-hot-store-split.js +600 -0
- package/scripts/colab-compute-honesty.js +281 -0
- package/scripts/deeppattern-discipline-honesty.js +431 -0
- package/scripts/gates-engine.js +16 -5
- package/scripts/install-thumbgate-daily-publish-launchd.sh +83 -0
- package/scripts/llm-obs-honesty.js +478 -0
- package/scripts/thumbgate-board-loop.js +432 -0
- package/scripts/thumbgate-daily-discoveries-publish.js +413 -0
- package/scripts/thumbgate-daily-discoveries-publish.sh +18 -0
- package/scripts/token-shunt-honesty.js +502 -0
- package/scripts/typesafe-typed-questions.js +1138 -0
- package/server.json +2 -2
- package/skills/article-syndication-pipeline/SKILL.md +37 -0
- package/skills/comet-session-logger/SKILL.md +59 -0
- package/skills/thumbgate-daily-discoveries/SKILL.md +57 -0
- package/src/index.js +10 -0
- package/src/integrations/ideabrowser-connector.js +190 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DeepPattern (AQG + Decision Engine) FORMAT steal — not a clone.
|
|
6
|
+
*
|
|
7
|
+
* Sources (public, MIT, 2026-09):
|
|
8
|
+
* https://github.com/deeppatternai/agent-quality-gates
|
|
9
|
+
* https://github.com/deeppatternai/decision-engine
|
|
10
|
+
*
|
|
11
|
+
* Transfers:
|
|
12
|
+
* 1. layer-check — refuse treating products at different stack layers as substitutes
|
|
13
|
+
* (DE /layer-check). Primary failure: L4 transport/API gateway as "competitor" to
|
|
14
|
+
* L7 PreToolUse governance; or multi-model panel SaaS as ThumbGate substitute.
|
|
15
|
+
* 2. evidence-closeout — six required evidence items before claiming done
|
|
16
|
+
* (AQG check_evidence_closeout). Maps onto ThumbGate Completion Claim Contract.
|
|
17
|
+
*
|
|
18
|
+
* Does NOT install AQG, Decision Engine, dp-install, or a cross-vendor audit SKU.
|
|
19
|
+
* Does NOT unpark LLM adjudicator (#3690/#3687). Hosted multi-model panels stay external.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('node:fs');
|
|
23
|
+
const path = require('node:path');
|
|
24
|
+
|
|
25
|
+
const SOURCE_AQG = 'https://github.com/deeppatternai/agent-quality-gates';
|
|
26
|
+
const SOURCE_DE = 'https://github.com/deeppatternai/decision-engine';
|
|
27
|
+
|
|
28
|
+
const LAYERS = Object.freeze({
|
|
29
|
+
L1: 'Metal / hardware',
|
|
30
|
+
L2: 'Foundation model',
|
|
31
|
+
L3: 'Training / eval infrastructure',
|
|
32
|
+
L4: 'Transport / API gateway',
|
|
33
|
+
L5: 'Orchestration framework',
|
|
34
|
+
L6: 'Developer tooling platform',
|
|
35
|
+
L7: 'Application / workflow SaaS',
|
|
36
|
+
L8: 'Brand / distribution surface',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const THUMBGATE_LAYER = 'L7';
|
|
40
|
+
|
|
41
|
+
const REQUIRED_CLOSEOUT_ITEMS = Object.freeze([
|
|
42
|
+
'scope completed',
|
|
43
|
+
'verification run',
|
|
44
|
+
'audit adjudicated',
|
|
45
|
+
'durable state updated',
|
|
46
|
+
'production boundary',
|
|
47
|
+
'remaining blockers',
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const EMPTY_CLOSEOUT_VALUES = new Set([
|
|
51
|
+
'', '-', '--', 'n/a', 'na', 'todo', 'tbd', 'pending',
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const CLONE_RE = /\b(dp-install|agent-quality-gates|decision-engine|aqg_doctor|deeppattern|install_aqg|\/audit-forecast|\/discussion-board)\b/i;
|
|
55
|
+
const SUBSTITUTE_RE = /\b(replace[sd]?|substitut\w*|commoditiz\w*|competes? with|instead of|drop thumbgate for|thumbgate is just|same as thumbgate)\b/i;
|
|
56
|
+
const CROSS_LAYER_SUBJECTS = [
|
|
57
|
+
{ re: /\b(api gateway|token aggregat\w*|openrouter|litellm|multi-?vendor (api|token)|raw (model )?api)\b/i, layer: 'L4', label: 'transport / API gateway' },
|
|
58
|
+
{ re: /\b(langchain|langgraph|crewai|autogen|agent framework|orchestration framework)\b/i, layer: 'L5', label: 'orchestration framework' },
|
|
59
|
+
{ re: /\b(foundation model|gpt-5|claude-4|raw llm|base model family)\b/i, layer: 'L2', label: 'foundation model' },
|
|
60
|
+
{ re: /\b(decision engine|cross-vendor panel|multi-?model (panel|audit)|hosted audit engine)\b/i, layer: 'L7_adjacent', label: 'hosted multi-model panel (complement, not PreToolUse gate)' },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const RAIL_MAP = Object.freeze([
|
|
64
|
+
{
|
|
65
|
+
deeppattern: 'layer-check: same job-to-be-done before calling something a substitute',
|
|
66
|
+
thumbgate: 'PreToolUse evaluate→block→evidence is L7 governance; L4 gateways are inputs',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
deeppattern: 'AQG evidence-closeout: six required items before "done"',
|
|
70
|
+
thumbgate: 'Completion Claim Contract — merge SHA / /health.buildSha / terminal CI',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
deeppattern: 'AQG local discipline + DE hosted panel as a pair',
|
|
74
|
+
thumbgate: 'Deterministic gates stay local; hosted multi-model audit is optional external',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
deeppattern: 'Never invent forecast numbers; pool existing sources',
|
|
78
|
+
thumbgate: 'Never invent ROI; never claim LIVE without same-session evidence',
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
function normalizeBoolean(value) {
|
|
83
|
+
if (value === true || value === 1) return true;
|
|
84
|
+
if (value === false || value === 0 || value == null) return false;
|
|
85
|
+
return /^(1|true|yes|on)$/i.test(String(value).trim());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeLabel(value) {
|
|
89
|
+
return String(value || '')
|
|
90
|
+
.trim()
|
|
91
|
+
.replace(/[*_`|]/g, '')
|
|
92
|
+
.replace(/[^a-z0-9 \-/]/gi, ' ')
|
|
93
|
+
.replace(/[/-]/g, ' ')
|
|
94
|
+
.replace(/\s+/g, ' ')
|
|
95
|
+
.trim()
|
|
96
|
+
.toLowerCase();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isPlaceholder(value) {
|
|
100
|
+
const stripped = String(value || '').trim();
|
|
101
|
+
const lowered = stripped.toLowerCase();
|
|
102
|
+
const normalized = normalizeLabel(stripped);
|
|
103
|
+
if (EMPTY_CLOSEOUT_VALUES.has(normalized)) return true;
|
|
104
|
+
if (/\b(todo|tbd)\b/.test(lowered)) return true;
|
|
105
|
+
if (/__fill/.test(lowered)) return true;
|
|
106
|
+
if (/<[^>]+>/.test(stripped)) return true;
|
|
107
|
+
const compact = lowered.replace(/[^a-z0-9]/g, '');
|
|
108
|
+
return compact.length > 0 && compact.length < 3;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function headingSpans(text) {
|
|
112
|
+
const pattern = /^(#{1,6})\s+(.+?)\s*$/gm;
|
|
113
|
+
const matches = [...String(text || '').matchAll(pattern)];
|
|
114
|
+
const spans = [];
|
|
115
|
+
for (let i = 0; i < matches.length; i += 1) {
|
|
116
|
+
const match = matches[i];
|
|
117
|
+
const start = match.index + match[0].length;
|
|
118
|
+
const end = i + 1 < matches.length ? matches[i + 1].index : text.length;
|
|
119
|
+
spans.push({ title: match[2].trim(), body: text.slice(start, end) });
|
|
120
|
+
}
|
|
121
|
+
return spans;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isEvidenceHeading(title) {
|
|
125
|
+
const normalized = normalizeLabel(title);
|
|
126
|
+
return normalized.includes('evidence') || normalized.includes('closeout') || title.includes('证据');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function extractCloseoutItems(text) {
|
|
130
|
+
const blocks = headingSpans(text)
|
|
131
|
+
.filter((span) => isEvidenceHeading(span.title))
|
|
132
|
+
.map((span) => span.body);
|
|
133
|
+
if (!blocks.length && REQUIRED_CLOSEOUT_ITEMS.some((item) => normalizeLabel(text).includes(item))) {
|
|
134
|
+
blocks.push(text);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const found = {};
|
|
138
|
+
for (const block of blocks) {
|
|
139
|
+
for (const line of block.split(/\r?\n/)) {
|
|
140
|
+
const trimmed = line.trim();
|
|
141
|
+
if (!trimmed) continue;
|
|
142
|
+
if (trimmed.startsWith('|') && trimmed.endsWith('|')) {
|
|
143
|
+
const cells = trimmed.slice(1, -1).split('|').map((c) => c.trim());
|
|
144
|
+
if (cells.length < 2) continue;
|
|
145
|
+
if (cells.every((c) => /^[-:\s]+$/.test(c))) continue;
|
|
146
|
+
const label = normalizeLabel(cells[0]);
|
|
147
|
+
if (REQUIRED_CLOSEOUT_ITEMS.includes(label)) {
|
|
148
|
+
found[label] = cells.slice(1).join(' | ').trim();
|
|
149
|
+
}
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const colon = /^(?:\*\*)?([^:*|]{3,80}?)(?:\*\*)?\s*:\s*(.+)$/.exec(
|
|
153
|
+
trimmed.replace(/^[-*]\s+/, '')
|
|
154
|
+
);
|
|
155
|
+
if (!colon) continue;
|
|
156
|
+
const label = normalizeLabel(colon[1]);
|
|
157
|
+
if (REQUIRED_CLOSEOUT_ITEMS.includes(label)) {
|
|
158
|
+
found[label] = colon[2].trim();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
hasHeading: headingSpans(text).some((span) => isEvidenceHeading(span.title)),
|
|
164
|
+
found,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeOptions(raw = {}) {
|
|
169
|
+
return {
|
|
170
|
+
claim: String(raw.claim || ''),
|
|
171
|
+
subjectLayer: String(raw['subject-layer'] || raw.subjectLayer || THUMBGATE_LAYER).toUpperCase(),
|
|
172
|
+
closeoutPath: raw.closeout || raw['closeout-path'] || raw.closeoutPath || null,
|
|
173
|
+
closeoutText: raw['closeout-text'] || raw.closeoutText || null,
|
|
174
|
+
mapOnly: normalizeBoolean(raw['map-only'] || raw.mapOnly),
|
|
175
|
+
claimReady: normalizeBoolean(raw['claim-ready'] || raw.claimReady),
|
|
176
|
+
cloneDeeppattern: normalizeBoolean(raw['clone-deeppattern'] || raw.cloneDeeppattern || raw['clone-aqg'] || raw.cloneAqg),
|
|
177
|
+
json: normalizeBoolean(raw.json),
|
|
178
|
+
strict: normalizeBoolean(raw.strict),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildDeeppatternDisciplineHonestyReport(rawOptions = {}) {
|
|
183
|
+
const options = normalizeOptions(rawOptions);
|
|
184
|
+
const findings = [];
|
|
185
|
+
const claim = options.claim;
|
|
186
|
+
|
|
187
|
+
if (options.cloneDeeppattern || CLONE_RE.test(claim)) {
|
|
188
|
+
findings.push({
|
|
189
|
+
id: 'deeppattern_sku_clone',
|
|
190
|
+
severity: 'fail',
|
|
191
|
+
gateId: 'refuse-deeppattern-sku-clone',
|
|
192
|
+
message: 'Refused cloning Agent Quality Gates / Decision Engine / dp-install as a ThumbGate SKU. Steal FORMAT only.',
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (claim && SUBSTITUTE_RE.test(claim)) {
|
|
197
|
+
for (const subject of CROSS_LAYER_SUBJECTS) {
|
|
198
|
+
if (!subject.re.test(claim)) continue;
|
|
199
|
+
const distanceNote = subject.layer === 'L7_adjacent'
|
|
200
|
+
? 'same-layer complement (hosted panel) ≠ PreToolUse evaluate→block→evidence substitute'
|
|
201
|
+
: `layer ${subject.layer} vs ThumbGate ${THUMBGATE_LAYER}`;
|
|
202
|
+
findings.push({
|
|
203
|
+
id: 'cross_layer_substitute',
|
|
204
|
+
severity: 'fail',
|
|
205
|
+
gateId: 'require-same-layer-comparison',
|
|
206
|
+
message: `Category error: treating ${subject.label} as a ThumbGate substitute (${distanceNote}). Tag layers; compare only same job-to-be-done.`,
|
|
207
|
+
subjectLayer: options.subjectLayer,
|
|
208
|
+
referenceLayer: subject.layer,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Explicit layer distance check when claim names a lower-layer product next to ThumbGate/governance
|
|
214
|
+
if (claim && /\b(thumbgate|pretooluse|prevention rule|evaluate.?block.?evidence)\b/i.test(claim)) {
|
|
215
|
+
for (const subject of CROSS_LAYER_SUBJECTS) {
|
|
216
|
+
if (subject.layer === 'L7_adjacent') continue;
|
|
217
|
+
if (!subject.re.test(claim)) continue;
|
|
218
|
+
if (findings.some((f) => f.id === 'cross_layer_substitute' && f.referenceLayer === subject.layer)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (/\b(input|dependenc|consumes?|routes? through|sits under|below)\b/i.test(claim)) {
|
|
222
|
+
continue; // honest dependency language is fine
|
|
223
|
+
}
|
|
224
|
+
if (/\b(compet\w*|substitut\w*|commoditiz\w*|replace[sd]?|instead of|vs\.?|versus)\b/i.test(claim)) {
|
|
225
|
+
findings.push({
|
|
226
|
+
id: 'cross_layer_substitute',
|
|
227
|
+
severity: 'fail',
|
|
228
|
+
gateId: 'require-same-layer-comparison',
|
|
229
|
+
message: `Cross-layer comparison: ${subject.label} (${subject.layer}) vs ThumbGate (${THUMBGATE_LAYER}). Use as dependency language, not competitor language.`,
|
|
230
|
+
subjectLayer: THUMBGATE_LAYER,
|
|
231
|
+
referenceLayer: subject.layer,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let closeout = null;
|
|
238
|
+
const closeoutSource = options.closeoutText
|
|
239
|
+
|| (options.closeoutPath ? fs.readFileSync(String(options.closeoutPath), 'utf8') : null);
|
|
240
|
+
if (closeoutSource != null) {
|
|
241
|
+
const extracted = extractCloseoutItems(closeoutSource);
|
|
242
|
+
closeout = {
|
|
243
|
+
hasHeading: extracted.hasHeading,
|
|
244
|
+
items: extracted.found,
|
|
245
|
+
missing: [],
|
|
246
|
+
placeholders: [],
|
|
247
|
+
};
|
|
248
|
+
if (!extracted.hasHeading) {
|
|
249
|
+
findings.push({
|
|
250
|
+
id: 'missing_evidence_heading',
|
|
251
|
+
severity: 'fail',
|
|
252
|
+
gateId: 'require-evidence-closeout-items',
|
|
253
|
+
message: 'Closeout markdown needs an ## Evidence / Closeout heading (AQG evidence-closeout FORMAT).',
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
for (const item of REQUIRED_CLOSEOUT_ITEMS) {
|
|
257
|
+
if (!(item in extracted.found)) {
|
|
258
|
+
closeout.missing.push(item);
|
|
259
|
+
findings.push({
|
|
260
|
+
id: 'missing_closeout_item',
|
|
261
|
+
severity: 'fail',
|
|
262
|
+
gateId: 'require-evidence-closeout-items',
|
|
263
|
+
message: `Missing evidence closeout item: ${item}`,
|
|
264
|
+
item,
|
|
265
|
+
});
|
|
266
|
+
} else if (isPlaceholder(extracted.found[item])) {
|
|
267
|
+
closeout.placeholders.push(item);
|
|
268
|
+
findings.push({
|
|
269
|
+
id: 'placeholder_closeout_item',
|
|
270
|
+
severity: 'fail',
|
|
271
|
+
gateId: 'require-evidence-closeout-items',
|
|
272
|
+
message: `Evidence closeout item has placeholder value: ${item}`,
|
|
273
|
+
item,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (options.claimReady && findings.length) {
|
|
280
|
+
findings.push({
|
|
281
|
+
id: 'claim_ready_blocked',
|
|
282
|
+
severity: 'fail',
|
|
283
|
+
gateId: 'require-same-layer-comparison',
|
|
284
|
+
message: '--claim-ready refused: clear layer-check and closeout findings first.',
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const failCount = findings.filter((f) => f.severity === 'fail').length;
|
|
289
|
+
const status = options.mapOnly && !failCount
|
|
290
|
+
? 'ready'
|
|
291
|
+
: (failCount ? 'fail' : 'ready');
|
|
292
|
+
|
|
293
|
+
const nextActions = [];
|
|
294
|
+
if (findings.some((f) => f.id === 'deeppattern_sku_clone')) {
|
|
295
|
+
nextActions.push('Keep AQG/DE as neighbors. Steal layer-check + evidence-closeout FORMAT only.');
|
|
296
|
+
}
|
|
297
|
+
if (findings.some((f) => f.id === 'cross_layer_substitute')) {
|
|
298
|
+
nextActions.push('Retag each product L1–L8; drop cross-layer substitutes; cite specific surfaces not company names.');
|
|
299
|
+
}
|
|
300
|
+
if (findings.some((f) => f.gateId === 'require-evidence-closeout-items')) {
|
|
301
|
+
nextActions.push('Add an Evidence/Closeout heading with all six required items filled from fresh checks.');
|
|
302
|
+
}
|
|
303
|
+
if (!nextActions.length) {
|
|
304
|
+
nextActions.push('Use --map-only for the rail map; pass --closeout=path.md before claiming done.');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
name: 'thumbgate-deeppattern-discipline-honesty',
|
|
309
|
+
ok: status === 'ready',
|
|
310
|
+
status,
|
|
311
|
+
source: { aqg: SOURCE_AQG, decisionEngine: SOURCE_DE },
|
|
312
|
+
thumbgateLayer: THUMBGATE_LAYER,
|
|
313
|
+
layers: LAYERS,
|
|
314
|
+
requiredCloseoutItems: REQUIRED_CLOSEOUT_ITEMS,
|
|
315
|
+
map: options.mapOnly || !findings.length ? RAIL_MAP : undefined,
|
|
316
|
+
closeout,
|
|
317
|
+
findings,
|
|
318
|
+
summary: {
|
|
319
|
+
findingCount: findings.length,
|
|
320
|
+
failCount,
|
|
321
|
+
},
|
|
322
|
+
nextActions,
|
|
323
|
+
exampleCommand: 'npx thumbgate deeppattern-discipline-honesty --json --map-only',
|
|
324
|
+
disclaimer: 'FORMAT steal from DeepPattern Agent Quality Gates + Decision Engine. Not affiliated. Does not install AQG/DE or ship a cross-vendor audit SKU.',
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function formatDeeppatternDisciplineHonestyReport(report) {
|
|
329
|
+
const lines = [
|
|
330
|
+
'DeepPattern Discipline Honesty Doctor',
|
|
331
|
+
`Status : ${report.status}`,
|
|
332
|
+
`Layer : ThumbGate ${report.thumbgateLayer} (application / workflow governance)`,
|
|
333
|
+
`Sources : ${report.source.aqg}`,
|
|
334
|
+
` ${report.source.decisionEngine}`,
|
|
335
|
+
`Findings: ${report.summary.findingCount} (fail=${report.summary.failCount})`,
|
|
336
|
+
];
|
|
337
|
+
if (report.map) {
|
|
338
|
+
lines.push('', 'Rail map:');
|
|
339
|
+
for (const row of report.map) {
|
|
340
|
+
lines.push(` - ${row.deeppattern}`);
|
|
341
|
+
lines.push(` → ${row.thumbgate}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (report.closeout) {
|
|
345
|
+
lines.push('', 'Closeout:');
|
|
346
|
+
lines.push(` heading: ${report.closeout.hasHeading ? 'yes' : 'no'}`);
|
|
347
|
+
lines.push(` missing: ${report.closeout.missing.join(', ') || 'none'}`);
|
|
348
|
+
lines.push(` placeholders: ${report.closeout.placeholders.join(', ') || 'none'}`);
|
|
349
|
+
}
|
|
350
|
+
if (report.findings.length) {
|
|
351
|
+
lines.push('', 'Findings:');
|
|
352
|
+
for (const f of report.findings) {
|
|
353
|
+
lines.push(` - [${f.severity}] ${f.id}${f.gateId ? ` [${f.gateId}]` : ''}`);
|
|
354
|
+
lines.push(` ${f.message}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
lines.push('', 'Next actions:');
|
|
358
|
+
for (const a of report.nextActions) lines.push(` - ${a}`);
|
|
359
|
+
lines.push('', `Example: ${report.exampleCommand}`);
|
|
360
|
+
lines.push(`Note: ${report.disclaimer}`, '');
|
|
361
|
+
return `${lines.join('\n')}\n`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function parseCliArgs(argv) {
|
|
365
|
+
const options = {};
|
|
366
|
+
for (const arg of argv) {
|
|
367
|
+
if (arg === '--json') { options.json = true; continue; }
|
|
368
|
+
if (arg === '--strict') { options.strict = true; continue; }
|
|
369
|
+
if (arg === '--map-only') { options['map-only'] = true; continue; }
|
|
370
|
+
if (arg === '--claim-ready') { options['claim-ready'] = true; continue; }
|
|
371
|
+
if (arg === '--clone-deeppattern' || arg === '--clone-aqg') {
|
|
372
|
+
options['clone-deeppattern'] = true;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (arg === '--help' || arg === '-h') { options.help = true; continue; }
|
|
376
|
+
const m = /^--([^=]+)(?:=(.*))?$/.exec(arg);
|
|
377
|
+
if (!m) continue;
|
|
378
|
+
options[m[1]] = m[2] === undefined ? true : m[2];
|
|
379
|
+
}
|
|
380
|
+
return options;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function printHelp() {
|
|
384
|
+
process.stdout.write(`Usage: node scripts/deeppattern-discipline-honesty.js [flags]
|
|
385
|
+
|
|
386
|
+
Flags:
|
|
387
|
+
--claim=TEXT Comparative / substitute claim to layer-check
|
|
388
|
+
--subject-layer=L7 Subject product layer (default L7)
|
|
389
|
+
--closeout=PATH Markdown closeout / PR body to validate
|
|
390
|
+
--closeout-text=TEXT Inline closeout markdown
|
|
391
|
+
--map-only
|
|
392
|
+
--claim-ready
|
|
393
|
+
--clone-deeppattern Always fail (refuse AQG/DE SKU clone)
|
|
394
|
+
--json --strict
|
|
395
|
+
|
|
396
|
+
Sources:
|
|
397
|
+
${SOURCE_AQG}
|
|
398
|
+
${SOURCE_DE}
|
|
399
|
+
`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function runCli(argv = process.argv.slice(2)) {
|
|
403
|
+
const args = parseCliArgs(argv);
|
|
404
|
+
if (args.help) {
|
|
405
|
+
printHelp();
|
|
406
|
+
return 0;
|
|
407
|
+
}
|
|
408
|
+
const report = buildDeeppatternDisciplineHonestyReport(args);
|
|
409
|
+
if (args.json) process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
410
|
+
else process.stdout.write(formatDeeppatternDisciplineHonestyReport(report));
|
|
411
|
+
if (args.strict && report.status !== 'ready') return 1;
|
|
412
|
+
if (report.status === 'fail') return 1;
|
|
413
|
+
return 0;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
module.exports = {
|
|
417
|
+
SOURCE_AQG,
|
|
418
|
+
SOURCE_DE,
|
|
419
|
+
LAYERS,
|
|
420
|
+
THUMBGATE_LAYER,
|
|
421
|
+
REQUIRED_CLOSEOUT_ITEMS,
|
|
422
|
+
RAIL_MAP,
|
|
423
|
+
buildDeeppatternDisciplineHonestyReport,
|
|
424
|
+
formatDeeppatternDisciplineHonestyReport,
|
|
425
|
+
extractCloseoutItems,
|
|
426
|
+
runCli,
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
if (path.resolve(process.argv[1] || '') === path.resolve(__filename)) {
|
|
430
|
+
process.exitCode = runCli();
|
|
431
|
+
}
|
package/scripts/gates-engine.js
CHANGED
|
@@ -146,6 +146,10 @@ const BOOSTED_RISK_MIN_EXAMPLES = 3;
|
|
|
146
146
|
const PR_THREAD_RESOLUTION_ACTION = 'pr_thread_resolution_verified_after_commit';
|
|
147
147
|
const HELPER_BYPASS_ACTION = 'helper_script_modified';
|
|
148
148
|
const KNOWLEDGE_ENTROPY_THRESHOLD = 0.7;
|
|
149
|
+
// Issue #3689: do not inject lessons that only barely matched. Retrieval already
|
|
150
|
+
// filters >0.1 for ranking; injection requires a higher bar so low-relevance
|
|
151
|
+
// memories cannot ride along with an entropy disclaimer.
|
|
152
|
+
const MIN_LESSON_INJECTION_RELEVANCE = 0.75;
|
|
149
153
|
// Generous character bound: keeps every affected file for realistic actions while still
|
|
150
154
|
// preventing an unbounded haystack. Chosen over a file-count cap, which dropped targets.
|
|
151
155
|
const MEMORY_GUARD_MAX_SERIALIZED_CHARS = 200000;
|
|
@@ -4452,7 +4456,11 @@ async function buildRelevantLessonContextAsync(toolName, toolInput) {
|
|
|
4452
4456
|
* negative lesson present is relevant enough to surface.
|
|
4453
4457
|
*/
|
|
4454
4458
|
function formatNegativeLessonContext(lessons) {
|
|
4455
|
-
const negative = (lessons || []).filter((l) =>
|
|
4459
|
+
const negative = (lessons || []).filter((l) => {
|
|
4460
|
+
if (l.signal !== 'negative') return false;
|
|
4461
|
+
const score = Number(l.rerankedScore ?? l.relevanceScore ?? 0);
|
|
4462
|
+
return score >= MIN_LESSON_INJECTION_RELEVANCE;
|
|
4463
|
+
});
|
|
4456
4464
|
if (negative.length === 0) return null;
|
|
4457
4465
|
|
|
4458
4466
|
const formatted = negative.map((l) => {
|
|
@@ -4477,8 +4485,10 @@ function isKnowledgeConflictHardBlockAction(toolName, toolInput = {}) {
|
|
|
4477
4485
|
}
|
|
4478
4486
|
|
|
4479
4487
|
function buildKnowledgeConflictContext(toolName, toolInput, lessons, entropy) {
|
|
4480
|
-
|
|
4481
|
-
|
|
4488
|
+
// Issue #3689: high entropy means the scorer disagrees with itself. Do NOT
|
|
4489
|
+
// inject a disclaimer + noisy lessons into unrelated tool calls — suppress.
|
|
4490
|
+
// Strict mode may still hard-block destructive/external side effects.
|
|
4491
|
+
const message = `Knowledge conflict: retrieved lessons disagree for this action (entropy ${entropy}).`;
|
|
4482
4492
|
|
|
4483
4493
|
if (isKnowledgeConflictHardBlockAction(toolName, toolInput)) {
|
|
4484
4494
|
recordStat('retrieval_entropy_high', 'block', null, { toolName, toolInput });
|
|
@@ -4490,8 +4500,9 @@ function buildKnowledgeConflictContext(toolName, toolInput, lessons, entropy) {
|
|
|
4490
4500
|
};
|
|
4491
4501
|
}
|
|
4492
4502
|
|
|
4493
|
-
|
|
4494
|
-
|
|
4503
|
+
// Count as log (not warn): we intentionally did not inject context.
|
|
4504
|
+
recordStat('retrieval_entropy_high', 'log', null, { toolName, toolInput });
|
|
4505
|
+
return null;
|
|
4495
4506
|
}
|
|
4496
4507
|
|
|
4497
4508
|
function extractActionContext(toolName, toolInput) {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# install-thumbgate-daily-publish-launchd.sh — Daily ThumbGate News & Technical Discoveries LaunchAgent.
|
|
3
|
+
#
|
|
4
|
+
# Default: every day at 09:00 AM EST (local).
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# bash scripts/install-thumbgate-daily-publish-launchd.sh
|
|
8
|
+
# bash scripts/install-thumbgate-daily-publish-launchd.sh --uninstall
|
|
9
|
+
#
|
|
10
|
+
# Logs: ~/Library/Logs/thumbgate-daily-publish.log
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
14
|
+
SCRIPT="$ROOT/scripts/thumbgate-daily-discoveries-publish.sh"
|
|
15
|
+
LABEL="com.thumbgate.daily-publish"
|
|
16
|
+
PLIST="$HOME/Library/LaunchAgents/${LABEL}.plist"
|
|
17
|
+
LOG_DIR="$HOME/Library/Logs"
|
|
18
|
+
HOUR="${THUMBGATE_PUBLISH_HOUR:-9}"
|
|
19
|
+
MINUTE="${THUMBGATE_PUBLISH_MINUTE:-0}"
|
|
20
|
+
|
|
21
|
+
UNINSTALL=0
|
|
22
|
+
while [[ $# -gt 0 ]]; do
|
|
23
|
+
case "$1" in
|
|
24
|
+
--uninstall) UNINSTALL=1 ;;
|
|
25
|
+
-h|--help)
|
|
26
|
+
echo "Usage: $0 [--uninstall]"
|
|
27
|
+
exit 0
|
|
28
|
+
;;
|
|
29
|
+
*)
|
|
30
|
+
echo "unknown arg: $1" >&2
|
|
31
|
+
exit 2
|
|
32
|
+
;;
|
|
33
|
+
esac
|
|
34
|
+
shift
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
if [[ "$(uname -s)" != "Darwin" ]]; then
|
|
38
|
+
echo "error: macOS only" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
if [[ "$UNINSTALL" -eq 1 ]]; then
|
|
43
|
+
launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null || true
|
|
44
|
+
rm -f "$PLIST"
|
|
45
|
+
echo "removed $PLIST"
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
mkdir -p "$LOG_DIR" "$HOME/Library/LaunchAgents"
|
|
50
|
+
|
|
51
|
+
cat > "$PLIST" <<PLIST_EOF
|
|
52
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
53
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
54
|
+
<plist version="1.0">
|
|
55
|
+
<dict>
|
|
56
|
+
<key>Label</key>
|
|
57
|
+
<string>${LABEL}</string>
|
|
58
|
+
<key>ProgramArguments</key>
|
|
59
|
+
<array>
|
|
60
|
+
<string>/bin/bash</string>
|
|
61
|
+
<string>${SCRIPT}</string>
|
|
62
|
+
</array>
|
|
63
|
+
<key>StartCalendarInterval</key>
|
|
64
|
+
<dict>
|
|
65
|
+
<key>Hour</key>
|
|
66
|
+
<integer>${HOUR}</integer>
|
|
67
|
+
<key>Minute</key>
|
|
68
|
+
<integer>${MINUTE}</integer>
|
|
69
|
+
</dict>
|
|
70
|
+
<key>StandardOutPath</key>
|
|
71
|
+
<string>${LOG_DIR}/thumbgate-daily-publish.log</string>
|
|
72
|
+
<key>StandardErrorPath</key>
|
|
73
|
+
<string>${LOG_DIR}/thumbgate-daily-publish.log</string>
|
|
74
|
+
<key>WorkingDirectory</key>
|
|
75
|
+
<string>${ROOT}</string>
|
|
76
|
+
</dict>
|
|
77
|
+
</plist>
|
|
78
|
+
PLIST_EOF
|
|
79
|
+
|
|
80
|
+
launchctl bootout "gui/$(id -u)/${LABEL}" 2>/dev/null || true
|
|
81
|
+
launchctl bootstrap "gui/$(id -u)" "$PLIST"
|
|
82
|
+
|
|
83
|
+
echo "installed and loaded ${PLIST} (runs daily at ${HOUR}:${MINUTE})"
|