thumbgate 1.37.2 → 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/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/typesafe-typed-questions-not-clone/SKILL.md +7 -3
- 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 +132 -4
- package/config/gate-templates.json +48 -0
- package/package.json +36 -9
- 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 +51 -1
- package/scripts/deeppattern-discipline-honesty.js +431 -0
- 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/typesafe-typed-questions.js +332 -7
- 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
|
+
}
|
|
@@ -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})"
|