gm-skill 2.0.2571 → 2.0.2573
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.md +10 -0
- package/gm-plugkit/package.json +1 -1
- package/gm.json +1 -1
- package/package.json +1 -1
- package/scripts/scan-supply-chain-tells.mjs +257 -0
- package/skills/gm-semantic-anchors/SKILL.md +262 -0
package/AGENTS.md
CHANGED
|
@@ -172,6 +172,16 @@ A task that reduces to read/investigate/report, or a change confined to files th
|
|
|
172
172
|
|
|
173
173
|
**Bootstrap contract**: `ensureReady` inits wasm hook-free, sha256-rewrites stale installed SKILL.md, seeds per-project `CLAUDE.md`/`.gm/next-step.md`. Detail: the recall store (`recall: skill-initiated bootstrap contract`, `recall: SKILL.md auto-refresh`).
|
|
174
174
|
|
|
175
|
+
## Supply-chain tell scanner
|
|
176
|
+
|
|
177
|
+
`scripts/scan-supply-chain-tells.mjs` scans any file tree for the injected-backdoor/AI-tell signature classes found in a real incident on a consumer project (freddie): an obfuscated C2 stager appended after a legitimate `export default` in a build config, delivered via a compromised automated release commit that also bumped a dependency and dropped `.env` from `.gitignore`. Run it: `node scripts/scan-supply-chain-tells.mjs <path...>` (defaults to cwd). Exit 0 = clean, 1 = findings, 2 = scan error.
|
|
178
|
+
|
|
179
|
+
Detection classes: exact-match known-incident strings (campaign tag, hardcoded C2-lookup address, custom payload-smuggling header), structural patterns (blockchain-RPC-derived C2 address resolution, IP-from-bytes decode, detached+hidden-stdio child-process spawn, `eval()` fed by a network response, XOR-decode loops, `require`/`module` stashed onto `global`, a dense minified tail line appended to an otherwise normal readable file, `\uXXXX`-escaped ASCII used to defeat plaintext grep, multiple public RPC endpoints raced with `Promise.any` for C2 resilience), and Unicode confusables/invisible characters (zero-width spaces/joiners, bidi override characters — the Trojan Source class — soft hyphens, and common Cyrillic homoglyphs for Latin letters).
|
|
180
|
+
|
|
181
|
+
**Dispatch this scan at EMIT (before any live witness) and again at DECIDE's adversarial corner-case sweep, on every file the diff touches** — alongside the existing empty/overflow/injection/resource-exhaustion sweep classes, not instead of them. A `security` toolset dependency bump, an automated release commit, or any file with unexpectedly dense/obfuscated content are the highest-suspicion triggers for an out-of-band run against the full tree, not just the diff. A finding routes like any other DECIDE discovery: confirmed-malicious content is a `transition to=EMIT` fix (remove and re-witness), never a silent ignore or a "looks fine" without running the scan.
|
|
182
|
+
|
|
183
|
+
This is a heuristic signature scanner, not a guarantee — it catches the specific technique classes seen in the confirmed incident and known-adjacent variants; it does not replace `npm audit`, dependency pinning review, or reading an unfamiliar dependency's actual source before trusting an automated bump.
|
|
184
|
+
|
|
175
185
|
## Cascade pipeline
|
|
176
186
|
|
|
177
187
|
Push to any rs-* sibling -> `cascade.yml` -> rs-plugkit `release.yml` -> single `plugkit.wasm` (npm `plugkit-wasm` + `plugkit-bin` Releases) -> best-effort gm-metadata-sync commit -> `publish.yml` ships gm-skill+gm-plugkit+SKILL.md mirror. Step sequence + PUBLISHER_TOKEN: the recall store (`recall: cascade pipeline`).
|
package/gm-plugkit/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-plugkit",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2573",
|
|
4
4
|
"description": "Bootstrap and daemon-spawn tool for gm plugkit binary. Downloads the correct platform wasm, verifies SHA256, and launches agentplug-runner (the native wasm host) as the spool watcher daemon.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/gm.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gm-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2573",
|
|
4
4
|
"description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
|
|
5
5
|
"author": "AnEntrypoint",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Scans files for supply-chain-backdoor and AI-injection "tells" -- signatures
|
|
3
|
+
// found live in a real incident (an obfuscated C2 stager appended to
|
|
4
|
+
// vite.browser.config.js, delivered via a compromised automated release
|
|
5
|
+
// commit that also bumped a dependency and dropped .env from .gitignore).
|
|
6
|
+
//
|
|
7
|
+
// Run standalone: node scripts/scan-supply-chain-tells.mjs [path...]
|
|
8
|
+
// Exit code 0 = clean, 1 = findings, 2 = scan error.
|
|
9
|
+
//
|
|
10
|
+
// Designed to be invoked from any project (not just this one) -- pass the
|
|
11
|
+
// target repo root(s) as argv, or it scans cwd.
|
|
12
|
+
|
|
13
|
+
import fs from 'node:fs'
|
|
14
|
+
import path from 'node:path'
|
|
15
|
+
import process from 'node:process'
|
|
16
|
+
|
|
17
|
+
const SKIP_DIRS = new Set([
|
|
18
|
+
'node_modules', '.git', 'dist', 'build', '.next', 'vendor', '.cache',
|
|
19
|
+
'.svelte-kit', '.nuxt', '.output', '.turbo', 'out', 'coverage', '.parcel-cache',
|
|
20
|
+
])
|
|
21
|
+
// Code files only. JSON/YAML/MD routinely carry legitimate non-Latin natural-
|
|
22
|
+
// language text (Cyrillic, Greek, CJK, etc.) which is indistinguishable from a
|
|
23
|
+
// homoglyph attack by codepoint alone -- the Unicode-confusable check below is
|
|
24
|
+
// only meaningful applied to CODE, where an identifier/URL is expected to be
|
|
25
|
+
// plain ASCII and non-ASCII inside one is a genuine anomaly, not content.
|
|
26
|
+
const CODE_EXT = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.sh', '.ps1', '.py'])
|
|
27
|
+
// Broader set for the exact-string / pattern checks, which key on code shapes
|
|
28
|
+
// (require calls, eval, spawn) that can legitimately only appear in code or
|
|
29
|
+
// config, not prose -- config/build files are still worth the exact-string and
|
|
30
|
+
// structural-pattern passes, just not the Unicode-confusable pass.
|
|
31
|
+
const TEXT_EXT = new Set([...CODE_EXT, '.json', '.yml', '.yaml', '.md'])
|
|
32
|
+
|
|
33
|
+
// ---- Exact-match signatures from the confirmed incident ----------------
|
|
34
|
+
const EXACT_STRINGS = [
|
|
35
|
+
{ sig: 'A9-2057', why: 'campaign/version tag literal seen in a live C2 stager' },
|
|
36
|
+
{ sig: '0xa322E5f3D311D3080e6f0121063e9aDC2490Ef1a', why: 'hardcoded Ethereum address used as a blockchain-based C2 config lookup key' },
|
|
37
|
+
{ sig: 'eth.blockscout.com/api', why: 'block-explorer API used as a C2-resolution fallback' },
|
|
38
|
+
{ sig: 'x-payload-b64', why: 'custom HTTP header used to smuggle a staged payload' },
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
// ---- Structural / behavioral patterns (regex) ---------------------------
|
|
42
|
+
const PATTERNS = [
|
|
43
|
+
{
|
|
44
|
+
name: 'blockchain-derived-c2-lookup',
|
|
45
|
+
re: /eth_getBlockByNumber|eth_getTransactionCount|eth_blockNumber/,
|
|
46
|
+
why: 'blockchain RPC calls used to derive a C2 address/config are a known dead-drop technique — legitimate web3 code should be an isolated, obviously-named module, not appended to an unrelated build config',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'ip-from-bytes-decode',
|
|
50
|
+
re: /\[0\]\s*\+\s*['"]\.['"]\s*\+\s*\w+\[1\]\s*\+\s*['"]\.['"]\s*\+\s*\w+\[2\]/,
|
|
51
|
+
why: 'byte-array-to-dotted-IP decode shape, commonly used to hide a C2 address inside binary data (a tx field, an image, etc.)',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'detached-hidden-spawn',
|
|
55
|
+
re: /spawn\s*\(\s*['"]node['"][\s\S]{0,120}detached\s*:\s*true[\s\S]{0,120}stdio\s*:\s*['"]ignore['"]/,
|
|
56
|
+
why: 'detached + stdio:ignore + unref() child process launch — runs code that outlives and is invisible to the parent process',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'detached-hidden-spawn-loose',
|
|
60
|
+
re: /detached\s*:\s*true[\s\S]{0,60}(stdio\s*:\s*['"]ignore['"]|windowsHide\s*:\s*true)/,
|
|
61
|
+
why: 'detached background process with hidden stdio/window — legitimate daemonization exists but is rare in application/build code and should be named and commented',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'eval-of-network-response',
|
|
65
|
+
re: /eval\s*\(\s*[\w.]*\+?\s*(await\s+)?\w*(fetch|http|https|response|body|payload|res)\w*/i,
|
|
66
|
+
why: 'eval() fed directly by network response content — remote code execution primitive',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'xor-decode-helper',
|
|
70
|
+
re: /charCodeAt\(\w+\s*%\s*\w+\.length\)/,
|
|
71
|
+
why: 'per-byte XOR-against-key loop, the standard shape for decoding an obfuscated payload at runtime',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'global-require-module-stash',
|
|
75
|
+
re: /global\.\w+\s*=\s*require\s*;\s*typeof\s+module\s*===\s*['"]object['"]/,
|
|
76
|
+
why: 'stashing require/module onto global — used so eval()-ed code can access them outside its lexical scope',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'minified-tail-after-normal-code',
|
|
80
|
+
re: /\n[a-z]\.[a-z]\([a-z],[a-z],[a-z]\)/,
|
|
81
|
+
why: 'single-letter-identifier minified code appearing after normally-formatted source — mixing styles in one file is a strong injection tell',
|
|
82
|
+
weak: true,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'promise-any-rpc-race',
|
|
86
|
+
re: /Promise\.any\(\s*\[?\s*['"]https?:\/\/[^,\]]+,\s*['"]https?:\/\//,
|
|
87
|
+
why: 'racing multiple public RPC/API endpoints for resilience — common in C2 code designed to survive one endpoint being blocked',
|
|
88
|
+
},
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
// ---- Unicode confusables / invisible-character tells ---------------------
|
|
92
|
+
// Each entry: a *visible-as-ASCII-but-isn't* or fully invisible codepoint.
|
|
93
|
+
const SUSPICIOUS_UNICODE = [
|
|
94
|
+
{ cp: 0x200b, name: 'ZERO WIDTH SPACE' },
|
|
95
|
+
{ cp: 0x200c, name: 'ZERO WIDTH NON-JOINER' },
|
|
96
|
+
{ cp: 0x200d, name: 'ZERO WIDTH JOINER' },
|
|
97
|
+
{ cp: 0x200e, name: 'LEFT-TO-RIGHT MARK' },
|
|
98
|
+
{ cp: 0x200f, name: 'RIGHT-TO-LEFT MARK' },
|
|
99
|
+
{ cp: 0x202a, name: 'LEFT-TO-RIGHT EMBEDDING' },
|
|
100
|
+
{ cp: 0x202b, name: 'RIGHT-TO-LEFT EMBEDDING' },
|
|
101
|
+
{ cp: 0x202c, name: 'POP DIRECTIONAL FORMATTING' },
|
|
102
|
+
{ cp: 0x202d, name: 'LEFT-TO-RIGHT OVERRIDE' },
|
|
103
|
+
{ cp: 0x202e, name: 'RIGHT-TO-LEFT OVERRIDE (Trojan Source bidi attack)' },
|
|
104
|
+
{ cp: 0x2060, name: 'WORD JOINER' },
|
|
105
|
+
{ cp: 0x2066, name: 'LEFT-TO-RIGHT ISOLATE' },
|
|
106
|
+
{ cp: 0x2067, name: 'RIGHT-TO-LEFT ISOLATE' },
|
|
107
|
+
{ cp: 0x2068, name: 'FIRST STRONG ISOLATE' },
|
|
108
|
+
{ cp: 0x2069, name: 'POP DIRECTIONAL ISOLATE' },
|
|
109
|
+
{ cp: 0xfeff, name: 'ZERO WIDTH NO-BREAK SPACE / BOM (mid-file)' },
|
|
110
|
+
{ cp: 0x00ad, name: 'SOFT HYPHEN' },
|
|
111
|
+
// Common homoglyphs used to disguise identifiers/URLs (Cyrillic look-alikes)
|
|
112
|
+
{ cp: 0x0410, name: 'CYRILLIC CAPITAL А (looks like Latin A)' },
|
|
113
|
+
{ cp: 0x0430, name: 'CYRILLIC SMALL а (looks like Latin a)' },
|
|
114
|
+
{ cp: 0x0415, name: 'CYRILLIC CAPITAL Е (looks like Latin E)' },
|
|
115
|
+
{ cp: 0x0435, name: 'CYRILLIC SMALL е (looks like Latin e)' },
|
|
116
|
+
{ cp: 0x041e, name: 'CYRILLIC CAPITAL О (looks like Latin O)' },
|
|
117
|
+
{ cp: 0x043e, name: 'CYRILLIC SMALL о (looks like Latin o)' },
|
|
118
|
+
{ cp: 0x0420, name: 'CYRILLIC CAPITAL Р (looks like Latin P)' },
|
|
119
|
+
{ cp: 0x0440, name: 'CYRILLIC SMALL р (looks like Latin p)' },
|
|
120
|
+
{ cp: 0x0421, name: 'CYRILLIC CAPITAL С (looks like Latin C)' },
|
|
121
|
+
{ cp: 0x0441, name: 'CYRILLIC SMALL с (looks like Latin c)' },
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
// A file whose non-ASCII payload is almost entirely \uXXXX-style JS escape
|
|
125
|
+
// sequences decoding to plain ASCII is itself a tell (deliberate obfuscation
|
|
126
|
+
// to defeat plain-string grep, seen in the confirmed incident's http/https/
|
|
127
|
+
// url/child_process require() calls).
|
|
128
|
+
const ESCAPED_ASCII_RUN = /(\\u00[2-7][0-9a-fA-F]){6,}/
|
|
129
|
+
|
|
130
|
+
function walk(dir, out) {
|
|
131
|
+
let entries
|
|
132
|
+
try {
|
|
133
|
+
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
134
|
+
} catch {
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
for (const e of entries) {
|
|
138
|
+
if (SKIP_DIRS.has(e.name)) continue
|
|
139
|
+
const p = path.join(dir, e.name)
|
|
140
|
+
if (e.isDirectory()) {
|
|
141
|
+
walk(p, out)
|
|
142
|
+
} else if (e.isFile() && TEXT_EXT.has(path.extname(e.name))) {
|
|
143
|
+
out.push(p)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function scanFile(filePath) {
|
|
149
|
+
let text
|
|
150
|
+
try {
|
|
151
|
+
text = fs.readFileSync(filePath, 'utf8')
|
|
152
|
+
} catch {
|
|
153
|
+
return []
|
|
154
|
+
}
|
|
155
|
+
const findings = []
|
|
156
|
+
|
|
157
|
+
for (const { sig, why } of EXACT_STRINGS) {
|
|
158
|
+
if (text.includes(sig)) {
|
|
159
|
+
findings.push({ filePath, kind: 'exact-string', sig, why })
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const { name, re, why, weak } of PATTERNS) {
|
|
164
|
+
const m = text.match(re)
|
|
165
|
+
if (m) {
|
|
166
|
+
const line = text.slice(0, m.index).split('\n').length
|
|
167
|
+
findings.push({ filePath, kind: weak ? 'pattern-weak' : 'pattern', name, why, line })
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const lines = text.split('\n')
|
|
172
|
+
const nonBlank = lines.filter(l => l.trim().length > 0)
|
|
173
|
+
if (nonBlank.length >= 5) {
|
|
174
|
+
const lens = nonBlank.map(l => l.length)
|
|
175
|
+
const avgLen = lens.reduce((a, b) => a + b, 0) / lens.length
|
|
176
|
+
const lastLen = lens[lens.length - 1]
|
|
177
|
+
const looksMinified = /[;,]\s*[a-zA-Z_$][\w$]*\s*=/.test(nonBlank[nonBlank.length - 1]) &&
|
|
178
|
+
/\b(function|=>|require\(|const |let |var )\b/.test(nonBlank[nonBlank.length - 1])
|
|
179
|
+
if (lastLen > 2000 && lastLen > avgLen * 20 && looksMinified) {
|
|
180
|
+
findings.push({
|
|
181
|
+
filePath,
|
|
182
|
+
kind: 'pattern',
|
|
183
|
+
name: 'dense-minified-tail-line',
|
|
184
|
+
line: nonBlank.length,
|
|
185
|
+
why: `final non-blank line is ${lastLen} chars (${Math.round(lastLen / avgLen)}x the file's average line length) and looks like minified code — this is the exact shape of a payload appended to the end of an otherwise normal, readable file`,
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (ESCAPED_ASCII_RUN.test(text)) {
|
|
191
|
+
findings.push({
|
|
192
|
+
filePath,
|
|
193
|
+
kind: 'pattern',
|
|
194
|
+
name: 'escaped-ascii-obfuscation',
|
|
195
|
+
why: '6+ consecutive \\uXXXX escapes decoding to plain ASCII — deliberate string obfuscation to defeat plaintext grep (e.g. \\u0068\\u0074\\u0074\\u0070 = "http")',
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (CODE_EXT.has(path.extname(filePath))) {
|
|
200
|
+
// De-duplicate per (codepoint, line): a run of several confusables on one
|
|
201
|
+
// line (a whole disguised word) is one finding, not one per character.
|
|
202
|
+
const seen = new Set()
|
|
203
|
+
for (let i = 0; i < text.length; i++) {
|
|
204
|
+
const cp = text.codePointAt(i)
|
|
205
|
+
const hit = SUSPICIOUS_UNICODE.find(u => u.cp === cp)
|
|
206
|
+
if (hit) {
|
|
207
|
+
const line = text.slice(0, i).split('\n').length
|
|
208
|
+
const key = cp + ':' + line
|
|
209
|
+
if (seen.has(key)) continue
|
|
210
|
+
seen.add(key)
|
|
211
|
+
findings.push({
|
|
212
|
+
filePath,
|
|
213
|
+
kind: 'unicode',
|
|
214
|
+
name: hit.name,
|
|
215
|
+
codepoint: '0x' + cp.toString(16),
|
|
216
|
+
line,
|
|
217
|
+
why: 'invisible or confusable Unicode codepoint inside a code file — used to hide code from visual review or disguise an identifier/URL as something else',
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return findings
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function main() {
|
|
227
|
+
const targets = process.argv.slice(2)
|
|
228
|
+
const roots = targets.length ? targets : [process.cwd()]
|
|
229
|
+
const files = []
|
|
230
|
+
for (const root of roots) {
|
|
231
|
+
const stat = fs.existsSync(root) ? fs.statSync(root) : null
|
|
232
|
+
if (!stat) continue
|
|
233
|
+
if (stat.isDirectory()) walk(root, files)
|
|
234
|
+
else files.push(root)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const allFindings = []
|
|
238
|
+
for (const f of files) {
|
|
239
|
+
allFindings.push(...scanFile(f))
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (!allFindings.length) {
|
|
243
|
+
console.log(`scan-supply-chain-tells: clean (${files.length} files scanned)`)
|
|
244
|
+
process.exit(0)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
console.log(`scan-supply-chain-tells: ${allFindings.length} finding(s) across ${new Set(allFindings.map(f => f.filePath)).size} file(s)\n`)
|
|
248
|
+
for (const f of allFindings) {
|
|
249
|
+
const loc = f.line ? `:${f.line}` : ''
|
|
250
|
+
const label = f.sig || f.name
|
|
251
|
+
console.log(`[${f.kind}] ${f.filePath}${loc} — ${label}`)
|
|
252
|
+
console.log(` ${f.why}`)
|
|
253
|
+
}
|
|
254
|
+
process.exit(1)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
main()
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gm-semantic-anchors
|
|
3
|
+
description: The nonlinear backreferencing graph of every named technique gm's 9-phase prose already invokes (SPECIFY through UPDATE_DOCS), cross-referenced against the llm-coding/Semantic-Anchors catalog, plus the well-known techniques that catalog is missing. Primes gm's own fsm-propose-override self-reconfiguration: a proposed prose/graph override cites the anchor it strengthens or the gap it closes, not a bare paraphrase.
|
|
4
|
+
allowed-tools: Skill, Read, Write
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# gm-semantic-anchors
|
|
8
|
+
|
|
9
|
+
A semantic anchor is a named, attributed technique from well-known literature that activates an LLM's existing knowledge of that technique precisely and compactly -- "Use TDD, London School" over "write tests first, mock dependencies, work outside-in" (llm-coding/Semantic-Anchors, `what-qualifies-as-a-semantic-anchor`). gm's own phase prose already runs on this principle: each phase's "Preferences (named, narrow)" section is a curated anchor list. This skill is that list expressed as one graph, cross-checked against the public Semantic-Anchors reference (191 anchors, `github.com/llm-coding/Semantic-Anchors`), so a `fsm-propose-override` self-reconfiguration proposal can cite a precise anchor instead of composing new prose from scratch.
|
|
10
|
+
|
|
11
|
+
## Why this exists
|
|
12
|
+
|
|
13
|
+
`fsm-propose-override` (rs-plugkit, `orchestrator/fsm_propose.rs`) lets a session write its own `.gm/instructions/<key>.md` or `fsm/graph.json` override when `self-reconfig-candidate` fires. A proposal grounded in an attributed, well-known technique is falsifiable and reviewable; a proposal that paraphrases a vague intuition is not. This graph is the lookup table: given a phase and a friction pattern, which anchor already names the fix, and which anchors does it backreference for a fuller picture.
|
|
14
|
+
|
|
15
|
+
## The graph
|
|
16
|
+
|
|
17
|
+
99 anchors: 93 cross-matched by exact id against the public Semantic-Anchors catalog, 6 added because they are well-known, well-attributed techniques gm's own CONC (concurrency/performance) and RES (resilience)/STATE (correctness) phases depend on but the catalog does not yet list. Edges are the catalog's own `:related:` backreference field where fetched live from `docs/anchors/<id>.adoc`; nodes are grouped by the gm phase that names them, which is itself a valid nonlinear grouping axis (an anchor can serve a phase other than the one gm currently files it under).
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
flowchart LR
|
|
21
|
+
subgraph specify["SPECIFY"]
|
|
22
|
+
solid_principles["SOLID Principles<br/><small>Robert C. Martin</small>"]
|
|
23
|
+
solid_srp["SOLID-SRP<br/><small>Robert C. Martin</small>"]
|
|
24
|
+
clean_architecture["Clean Architecture<br/><small>Robert C. Martin</small>"]
|
|
25
|
+
vertical_slice_architecture["Vertical Slice Architecture<br/><small>Jimmy Bogard</small>"]
|
|
26
|
+
mikado_method["Mikado Method<br/><small>Ola Ellnestam</small>"]
|
|
27
|
+
spike_solution["Spike Solution<br/><small>Kent Beck</small>"]
|
|
28
|
+
thin_vertical_slice["Thin Vertical Slice<br/><small>Alistair Cockburn</small>"]
|
|
29
|
+
xy_problem["XY Problem Avoidance<br/><small>Mark Jason Dominus</small>"]
|
|
30
|
+
cynefin_framework["Cynefin Framework<br/><small>Dave Snowden</small>"]
|
|
31
|
+
wardley_mapping["Wardley Mapping<br/><small>Simon Wardley</small>"]
|
|
32
|
+
jobs_to_be_done["Jobs To Be Done<br/><small>Clayton Christensen</small>"]
|
|
33
|
+
occams_razor["Occam's Razor<br/><small>William of Ockham</small>"]
|
|
34
|
+
first_principles_thinking["First Principles Thinking<br/><small>Aristotle</small>"]
|
|
35
|
+
five_whys["Five Whys<br/><small>Taiichi Ohno</small>"]
|
|
36
|
+
feynman_technique["Feynman Technique<br/><small>Richard Feynman</small>"]
|
|
37
|
+
morphological_box["Morphological Box<br/><small>Fritz Zwicky</small>"]
|
|
38
|
+
swot["SWOT<br/><small>Albert Humphrey</small>"]
|
|
39
|
+
pugh_matrix["Pugh Matrix<br/><small>Stuart Pugh</small>"]
|
|
40
|
+
mece["MECE<br/><small>Barbara Minto</small>"]
|
|
41
|
+
ears_requirements["EARS<br/><small>Alistair Mavin</small>"]
|
|
42
|
+
invest["INVEST<br/><small>Bill Wake</small>"]
|
|
43
|
+
cockburn_use_cases["Cockburn Use Cases<br/><small>Alistair Cockburn</small>"]
|
|
44
|
+
prd["PRD<br/><small>Product Management Convention</small>"]
|
|
45
|
+
devils_advocate["Devil's Advocate<br/><small>Catholic Canonization Process</small>"]
|
|
46
|
+
goodharts_law["Goodhart's Law<br/><small>Charles Goodhart</small>"]
|
|
47
|
+
pert["PERT<br/><small>US Navy</small>"]
|
|
48
|
+
adr_according_to_nygard["ADR<br/><small>Michael Nygard</small>"]
|
|
49
|
+
quality_attribute_scenario["Quality Attribute Scenario<br/><small>Software Architecture Convention</small>"]
|
|
50
|
+
moscow["MoSCoW<br/><small>Dai Clegg</small>"]
|
|
51
|
+
end
|
|
52
|
+
subgraph prove["PROVE"]
|
|
53
|
+
chain_of_thought["Chain-of-Thought Reasoning<br/><small>Wei</small>"]
|
|
54
|
+
end
|
|
55
|
+
subgraph emit["EMIT"]
|
|
56
|
+
dry["DRY<br/><small>Andy Hunt</small>"]
|
|
57
|
+
kiss_principle["KISS Principle<br/><small>Kelly Johnson</small>"]
|
|
58
|
+
yagni["YAGNI<br/><small>Ron Jeffries</small>"]
|
|
59
|
+
single_level_of_abstraction_principle["SLAP<br/><small>Kent Beck</small>"]
|
|
60
|
+
law_of_demeter["Law of Demeter<br/><small>Ian Holland</small>"]
|
|
61
|
+
code_smells["Code Smells<br/><small>Kent Beck</small>"]
|
|
62
|
+
cohesion_criteria["Cohesion Criteria<br/><small>Larry Constantine</small>"]
|
|
63
|
+
iosp["IOSP<br/><small>Ralf Westphal</small>"]
|
|
64
|
+
mental_model_according_to_naur["Programming as Theory Building<br/><small>Peter Naur</small>"]
|
|
65
|
+
sota["SOTA<br/><small>General Convention</small>"]
|
|
66
|
+
effective_go["Effective Go<br/><small>The Go Team</small>"]
|
|
67
|
+
conways_law["Conway's Law<br/><small>Melvin Conway</small>"]
|
|
68
|
+
grasp["GRASP<br/><small>Craig Larman</small>"]
|
|
69
|
+
solid_dip["SOLID-DIP<br/><small>Robert C. Martin</small>"]
|
|
70
|
+
hexagonal_architecture["Hexagonal Architecture<br/><small>Alistair Cockburn</small>"]
|
|
71
|
+
arc42["arc42<br/><small>Peter Hruschka</small>"]
|
|
72
|
+
cap_theorem["CAP Theorem<br/><small>Eric Brewer</small>"]
|
|
73
|
+
fallacies_of_distributed_computing["Fallacies of Distributed Computing<br/><small>Peter Deutsch</small>"]
|
|
74
|
+
event_driven_architecture["Event-Driven Architecture<br/><small>Distributed Systems Convention</small>"]
|
|
75
|
+
walking_skeleton["Walking Skeleton<br/><small>Alistair Cockburn</small>"]
|
|
76
|
+
gof_facade_pattern["GoF-Facade<br/><small>Gamma Helm Johnson Vlissides</small>"]
|
|
77
|
+
gof_adapter_pattern["GoF-Adapter<br/><small>Gamma Helm Johnson Vlissides</small>"]
|
|
78
|
+
gof_chain_of_responsibility_pattern["GoF-Chain of Responsibility<br/><small>Gamma Helm Johnson Vlissides</small>"]
|
|
79
|
+
gof_observer_pattern["GoF-Observer<br/><small>Gamma Helm Johnson Vlissides</small>"]
|
|
80
|
+
gof_strategy_pattern["GoF-Strategy<br/><small>Gamma Helm Johnson Vlissides</small>"]
|
|
81
|
+
bem_methodology["BEM Methodology<br/><small>Yandex</small>"]
|
|
82
|
+
conventional_commits["Conventional Commits<br/><small>Community Specification</small>"]
|
|
83
|
+
github_flow["GitHub Flow<br/><small>GitHub</small>"]
|
|
84
|
+
end
|
|
85
|
+
subgraph state["STATE"]
|
|
86
|
+
fagan_inspection["Fagan Inspection<br/><small>Michael Fagan</small>"]
|
|
87
|
+
property_based_testing["Property-Based Testing<br/><small>Koen Claessen</small>"]
|
|
88
|
+
mutation_testing["Mutation Testing<br/><small>Richard Lipton</small>"]
|
|
89
|
+
red_green_tdd["Red/Green TDD<br/><small>Kent Beck</small>"]
|
|
90
|
+
tdd_chicago_school["TDD Chicago School<br/><small>Chicago/Detroit Tradition</small>"]
|
|
91
|
+
test_double_meszaros["Test Double<br/><small>Gerard Meszaros</small>"]
|
|
92
|
+
testing_pyramid["Testing Pyramid<br/><small>Mike Cohn</small>"]
|
|
93
|
+
end
|
|
94
|
+
subgraph conc["CONC"]
|
|
95
|
+
end
|
|
96
|
+
subgraph sec["SEC"]
|
|
97
|
+
owasp_top_10["OWASP Top 10<br/><small>OWASP Foundation</small>"]
|
|
98
|
+
stride["STRIDE Threat Model<br/><small>Loren Kohnfelder</small>"]
|
|
99
|
+
postels_law["Postel's Law<br/><small>Jon Postel</small>"]
|
|
100
|
+
linddun["LINDDUN Privacy Threat Model<br/><small>KU Leuven</small>"]
|
|
101
|
+
iec_61508_sil_levels["IEC 61508 SIL Levels<br/><small>IEC</small>"]
|
|
102
|
+
regulated_environment["Regulated Environment<br/><small>Compliance Convention</small>"]
|
|
103
|
+
end
|
|
104
|
+
subgraph res["RES"]
|
|
105
|
+
site_reliability_engineering["Site Reliability Engineering<br/><small>Ben Treynor</small>"]
|
|
106
|
+
end
|
|
107
|
+
subgraph decide["DECIDE"]
|
|
108
|
+
definition_of_done["Definition of Done<br/><small>Ken Schwaber</small>"]
|
|
109
|
+
llm_evaluations["LLM-Evaluations<br/><small>LLM Evaluation Practice</small>"]
|
|
110
|
+
iso_25010["ISO/IEC 25010<br/><small>ISO</small>"]
|
|
111
|
+
control_chart_shewhart["Control Chart<br/><small>Walter Shewhart</small>"]
|
|
112
|
+
nelson_rules["Nelson Rules<br/><small>Lloyd S. Nelson</small>"]
|
|
113
|
+
spc["SPC<br/><small>Walter Shewhart / W. Edwards Deming</small>"]
|
|
114
|
+
end
|
|
115
|
+
subgraph update_docs["UPDATE_DOCS"]
|
|
116
|
+
pyramid_principle["Pyramid Principle<br/><small>Barbara Minto</small>"]
|
|
117
|
+
bluf["BLUF<br/><small>US Military Doctrine</small>"]
|
|
118
|
+
inverted_pyramid_style["Inverted Pyramid Style<br/><small>Journalism Convention</small>"]
|
|
119
|
+
plain_english_strunk_white["Plain English<br/><small>William Strunk Jr</small>"]
|
|
120
|
+
aida_model["AIDA Model<br/><small>E. St. Elmo Lewis</small>"]
|
|
121
|
+
hemingway_bridge["Hemingway Bridge<br/><small>Ernest Hemingway</small>"]
|
|
122
|
+
diataxis_framework["Diataxis Framework<br/><small>Daniele Procida</small>"]
|
|
123
|
+
docs_as_code["Docs-as-Code<br/><small>Ralf D. Muller</small>"]
|
|
124
|
+
blooms_taxonomy["Bloom's Taxonomy<br/><small>Benjamin Bloom</small>"]
|
|
125
|
+
end
|
|
126
|
+
subgraph gaps["ADDED (missing from reference)"]
|
|
127
|
+
big_o_algorithmic_complexity["Big O Algorithmic Complexity<br/><small>Donald Knuth</small>"]:::gap
|
|
128
|
+
data_oriented_design["Data-Oriented Design<br/><small>Mike Acton</small>"]:::gap
|
|
129
|
+
mechanical_sympathy["Mechanical Sympathy<br/><small>Martin Thompson</small>"]:::gap
|
|
130
|
+
zero_cost_abstractions["Zero-Cost Abstractions<br/><small>Bjarne Stroustrup</small>"]:::gap
|
|
131
|
+
circuit_breaker["Circuit Breaker<br/><small>Michael Nygard</small>"]:::gap
|
|
132
|
+
mental_model_illegal_states["Make Illegal States Unrepresentable<br/><small>Yaron Minsky</small>"]:::gap
|
|
133
|
+
end
|
|
134
|
+
big_o_algorithmic_complexity -.->|belongs to| conc
|
|
135
|
+
data_oriented_design -.->|belongs to| conc
|
|
136
|
+
mechanical_sympathy -.->|belongs to| conc
|
|
137
|
+
zero_cost_abstractions -.->|belongs to| conc
|
|
138
|
+
circuit_breaker -.->|belongs to| res
|
|
139
|
+
mental_model_illegal_states -.->|belongs to| state
|
|
140
|
+
|
|
141
|
+
dry -.-> single_level_of_abstraction_principle
|
|
142
|
+
dry -.-> kiss_principle
|
|
143
|
+
dry -.-> yagni
|
|
144
|
+
kiss_principle -.-> yagni
|
|
145
|
+
kiss_principle -.-> solid_principles
|
|
146
|
+
yagni -.-> tdd_chicago_school
|
|
147
|
+
solid_principles -.-> clean_architecture
|
|
148
|
+
cynefin_framework -.-> wardley_mapping
|
|
149
|
+
xy_problem -.-> bluf
|
|
150
|
+
five_whys -.-> xy_problem
|
|
151
|
+
five_whys -.-> first_principles_thinking
|
|
152
|
+
mece -.-> pyramid_principle
|
|
153
|
+
mece -.-> bluf
|
|
154
|
+
mece -.-> morphological_box
|
|
155
|
+
adr_according_to_nygard -.-> arc42
|
|
156
|
+
mikado_method -.-> tdd_chicago_school
|
|
157
|
+
spike_solution -.-> walking_skeleton
|
|
158
|
+
spike_solution -.-> pugh_matrix
|
|
159
|
+
clean_architecture -.-> hexagonal_architecture
|
|
160
|
+
solid_srp -.-> solid_principles
|
|
161
|
+
solid_srp -.-> single_level_of_abstraction_principle
|
|
162
|
+
arc42 -.-> quality_attribute_scenario
|
|
163
|
+
conways_law -.-> cohesion_criteria
|
|
164
|
+
conways_law -.-> vertical_slice_architecture
|
|
165
|
+
grasp -.-> solid_principles
|
|
166
|
+
grasp -.-> clean_architecture
|
|
167
|
+
gof_observer_pattern -.-> gof_strategy_pattern
|
|
168
|
+
conventional_commits -.-> github_flow
|
|
169
|
+
conventional_commits -.-> definition_of_done
|
|
170
|
+
chain_of_thought -.-> first_principles_thinking
|
|
171
|
+
chain_of_thought -.-> feynman_technique
|
|
172
|
+
owasp_top_10 -.-> regulated_environment
|
|
173
|
+
owasp_top_10 -.-> iec_61508_sil_levels
|
|
174
|
+
stride -.-> owasp_top_10
|
|
175
|
+
stride -.-> regulated_environment
|
|
176
|
+
definition_of_done -.-> moscow
|
|
177
|
+
pyramid_principle -.-> bluf
|
|
178
|
+
pyramid_principle -.-> inverted_pyramid_style
|
|
179
|
+
bluf -.-> inverted_pyramid_style
|
|
180
|
+
bluf -.-> plain_english_strunk_white
|
|
181
|
+
occams_razor -.-> kiss_principle
|
|
182
|
+
occams_razor -.-> yagni
|
|
183
|
+
occams_razor -.-> five_whys
|
|
184
|
+
occams_razor -.-> mece
|
|
185
|
+
occams_razor -.-> devils_advocate
|
|
186
|
+
first_principles_thinking -.-> feynman_technique
|
|
187
|
+
wardley_mapping -.-> swot
|
|
188
|
+
invest -.-> moscow
|
|
189
|
+
ears_requirements -.-> cockburn_use_cases
|
|
190
|
+
ears_requirements -.-> invest
|
|
191
|
+
cockburn_use_cases -.-> arc42
|
|
192
|
+
cockburn_use_cases -.-> iso_25010
|
|
193
|
+
pert -.-> moscow
|
|
194
|
+
goodharts_law -.-> llm_evaluations
|
|
195
|
+
property_based_testing -.-> mutation_testing
|
|
196
|
+
property_based_testing -.-> testing_pyramid
|
|
197
|
+
mutation_testing -.-> testing_pyramid
|
|
198
|
+
red_green_tdd -.-> tdd_chicago_school
|
|
199
|
+
testing_pyramid -.-> tdd_chicago_school
|
|
200
|
+
fagan_inspection -.-> mutation_testing
|
|
201
|
+
fagan_inspection -.-> testing_pyramid
|
|
202
|
+
docs_as_code -.-> diataxis_framework
|
|
203
|
+
docs_as_code -.-> arc42
|
|
204
|
+
docs_as_code -.-> conventional_commits
|
|
205
|
+
diataxis_framework -.-> arc42
|
|
206
|
+
diataxis_framework -.-> inverted_pyramid_style
|
|
207
|
+
morphological_box -.-> pugh_matrix
|
|
208
|
+
swot -.-> pugh_matrix
|
|
209
|
+
swot -.-> moscow
|
|
210
|
+
devils_advocate -.-> five_whys
|
|
211
|
+
site_reliability_engineering -.-> five_whys
|
|
212
|
+
site_reliability_engineering -.-> spc
|
|
213
|
+
site_reliability_engineering -.-> control_chart_shewhart
|
|
214
|
+
vertical_slice_architecture -.-> clean_architecture
|
|
215
|
+
vertical_slice_architecture -.-> hexagonal_architecture
|
|
216
|
+
thin_vertical_slice -.-> walking_skeleton
|
|
217
|
+
thin_vertical_slice -.-> vertical_slice_architecture
|
|
218
|
+
walking_skeleton -.-> clean_architecture
|
|
219
|
+
walking_skeleton -.-> hexagonal_architecture
|
|
220
|
+
fallacies_of_distributed_computing -.-> cap_theorem
|
|
221
|
+
fallacies_of_distributed_computing -.-> event_driven_architecture
|
|
222
|
+
fallacies_of_distributed_computing -.-> hexagonal_architecture
|
|
223
|
+
cap_theorem -.-> event_driven_architecture
|
|
224
|
+
event_driven_architecture -.-> hexagonal_architecture
|
|
225
|
+
event_driven_architecture -.-> clean_architecture
|
|
226
|
+
postels_law -.-> solid_principles
|
|
227
|
+
postels_law -.-> event_driven_architecture
|
|
228
|
+
linddun -.-> owasp_top_10
|
|
229
|
+
linddun -.-> regulated_environment
|
|
230
|
+
|
|
231
|
+
classDef gap fill:#ffe4b3,stroke:#c77700,stroke-width:2px
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Reading the graph
|
|
235
|
+
|
|
236
|
+
- **Solid subgraph membership** = the gm phase whose prose already names this anchor (`gm-config/prose/<phase>.md`, `Preferences (named, narrow)`).
|
|
237
|
+
- **Dotted edges** = a real `:related:` backreference pulled live from `llm-coding/Semantic-Anchors`'s `docs/anchors/<id>.adoc` source, not invented. An edge existing means the two anchors compose: a proposal citing one should check the other.
|
|
238
|
+
- **`gaps` subgraph (orange)** = well-known, well-attributed techniques in real literature that gm's prose already depends on (CONC's whole performance vocabulary; RES's Circuit Breaker; STATE's illegal-states principle) but that are absent from the public Semantic-Anchors catalog as of this graph's construction. `CONC`'s own subgraph is empty for exactly this reason -- every anchor CONC currently names is a gap, not yet catalogued upstream.
|
|
239
|
+
|
|
240
|
+
## The 6 gaps, for upstream contribution
|
|
241
|
+
|
|
242
|
+
| id | name | author | belongs to |
|
|
243
|
+
| --- | --- | --- | --- |
|
|
244
|
+
| `big-o-algorithmic-complexity` | Big O Algorithmic Complexity | Donald Knuth | CONC |
|
|
245
|
+
| `data-oriented-design` | Data-Oriented Design | Mike Acton | CONC |
|
|
246
|
+
| `mechanical-sympathy` | Mechanical Sympathy | Martin Thompson | CONC |
|
|
247
|
+
| `zero-cost-abstractions` | Zero-Cost Abstractions | Bjarne Stroustrup | CONC |
|
|
248
|
+
| `circuit-breaker` | Circuit Breaker | Michael Nygard | RES |
|
|
249
|
+
| `mental-model-illegal-states` | Make Illegal States Unrepresentable | Yaron Minsky | STATE |
|
|
250
|
+
|
|
251
|
+
These are candidates for a PR against `llm-coding/Semantic-Anchors` (`docs/anchors/<id>.adoc`, following the existing frontmatter shape: `:categories:`, `:roles:`, `:proponents:`, `:tags:`, `:related:`, `:tier:`, `:definition:`), not a fork -- the catalog is a shared reference, and gm benefits from every project's anchor set staying converged on one upstream source rather than diverging per project.
|
|
252
|
+
|
|
253
|
+
## Using this graph to prime self-reconfiguration
|
|
254
|
+
|
|
255
|
+
When `self-reconfig-candidate` fires (a gate denial repeating past `policy.gate_repeat_escalate_threshold`), before composing an `fsm-propose-override` proposal:
|
|
256
|
+
|
|
257
|
+
1. Identify which phase the friction occurred in.
|
|
258
|
+
2. Walk that phase's subgraph here for an anchor that already names the missing discipline.
|
|
259
|
+
3. Follow its dotted edges one hop -- the adjacent anchors are the ones a reviewer will expect the proposal to also account for.
|
|
260
|
+
4. If nothing in the graph names the gap, that is itself signal: the friction is either genuinely novel (write the override in gm's own voice) or names a technique missing from both gm's prose and the upstream catalog (add it to the gaps table above, and consider it for the same upstream PR).
|
|
261
|
+
|
|
262
|
+
A proposal that cites an anchor id from this graph is reviewable against real, external, authored literature. A proposal that doesn't is asking a reviewer to trust prose alone -- exactly the gap `fsm-propose-override`'s `AskUserQuestion` authority guard exists to catch for graph/hook-bearing overrides, and the same discipline is worth applying informally to prose-only overrides too.
|