@mmerterden/multi-agent-pipeline 20.5.0 → 20.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/LICENSE +10 -0
- package/docs/facts.json +5 -4
- package/install/_dev-only-files.mjs +1 -0
- package/manifest.json +20 -18
- package/package.json +4 -3
- package/pipeline/lib/confusables.json +100 -0
- package/pipeline/lib/normalize-text.mjs +48 -0
- package/pipeline/lib/outbound-gate.mjs +10 -1
- package/pipeline/lib/redact.mjs +4 -1
- package/pipeline/lib/vercel-deploy.sh +5 -7
- package/pipeline/scripts/agent-guard.py +30 -1
- package/pipeline/scripts/pre-commit-check.sh +59 -16
- package/pipeline/scripts/pre-push-check.sh +3 -3
- package/pipeline/scripts/run-ui-tests.sh +5 -5
- package/pipeline/scripts/website-deploy-commit.sh +3 -2
- package/pipeline/skills/.skill-manifest.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,24 @@ Internal file-layout changes that don't affect the slash-command surface are sti
|
|
|
16
16
|
|
|
17
17
|
## [Unreleased]
|
|
18
18
|
|
|
19
|
+
## [20.6.0] - 2026-09-26
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `pipeline/lib/normalize-text.mjs` and `pipeline/lib/confusables.json`: NFKC, invisible characters removed and look-alike letters and dashes mapped to ASCII. `agent-guard.py` reads the same table.
|
|
24
|
+
- `eval-guard-robustness.mjs` (in `npm test`): each attribution trailer and every provider in `secret-patterns.json` is passed through disguises (zero-width space, word joiner, Cyrillic letter, non-breaking hyphen) and must still be blocked by `agent-guard`, the outbound gate, the commit hook and the redactor; ordinary Turkish text, emoji and Unicode in code must still pass.
|
|
25
|
+
- `check-rewrite-overlap.mjs` (maintainer tool, not shipped): the share of a rewritten file's 6-word sequences that also appear in its source, with a `--max` threshold.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- The npm `license` field is `MIT AND LicenseRef-PolyForm-Perimeter-1.0.0`, and `LICENSE` names the skills that stay under PolyForm Perimeter 1.0.0 (listed in `NOTICE-swift-ios-skills.md`). Nothing was removed; the label now matches what the package carries.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- `agent-guard` blocked an attribution trailer only when it was written in plain ASCII; one written with a zero-width space, a Cyrillic letter, a non-breaking hyphen or a fullwidth colon passed. The rule now also matches the folded text.
|
|
34
|
+
- The outbound gate, `redact.mjs` and `pre-commit-check.sh` recognise a credential that carries an invisible character. `smoke-secret-parity.sh` runs every provider with a zero-width space through both surfaces.
|
|
35
|
+
- The pre-commit high-entropy scan no longer blocks long identifiers made of words, such as the Info.plist keys `UIApplicationSceneManifest_Generation` and `UISupportedInterfaceOrientations` behind the `INFOPLIST_KEY_` build-setting prefix. A letters-only token passes only when its CamelCase, `_` and `-` segments read like words; random mixed-case tokens are still blocked, including letters-only ones that the previous CamelCase exemption let through. The leading `+` of a staged diff line is stripped before candidates are extracted, so a token at column 0 is judged by its own shape.
|
|
36
|
+
|
|
19
37
|
## [20.5.0] - 2026-09-25
|
|
20
38
|
|
|
21
39
|
### Added
|
package/LICENSE
CHANGED
|
@@ -19,3 +19,13 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Third-party content under another license
|
|
26
|
+
|
|
27
|
+
The skills listed in pipeline/skills/shared/external/NOTICE-swift-ios-skills.md
|
|
28
|
+
are derived from dpearson2699/swift-ios-skills and remain under the PolyForm
|
|
29
|
+
Perimeter License 1.0.0 (https://polyformproject.org/licenses/perimeter/1.0.0),
|
|
30
|
+
not the MIT License above. That license's Required Notice is carried in the
|
|
31
|
+
NOTICE file. Other vendored skills keep the licenses their NOTICE files name.
|
package/docs/facts.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "Generated by pipeline/scripts/gen-facts.mjs. Do not hand-edit: the site reads this, and a number edited here instead of at its source is the drift this file removes.",
|
|
3
|
-
"generatedAt": "2026-09-
|
|
4
|
-
"version": "20.
|
|
3
|
+
"generatedAt": "2026-09-26",
|
|
4
|
+
"version": "20.6.0",
|
|
5
5
|
"phaseSchema": 2,
|
|
6
6
|
"phases": [
|
|
7
7
|
{
|
|
@@ -40,13 +40,14 @@
|
|
|
40
40
|
"skillCountAll": 218,
|
|
41
41
|
"agentCount": 10,
|
|
42
42
|
"hosts": ["Claude Code", "Codex CLI", "Copilot CLI"],
|
|
43
|
-
"toolCount":
|
|
43
|
+
"toolCount": 124,
|
|
44
44
|
"toolCategories": {
|
|
45
45
|
"ios": 40,
|
|
46
46
|
"android": 31,
|
|
47
47
|
"web": 18,
|
|
48
48
|
"code": 8,
|
|
49
49
|
"design": 7,
|
|
50
|
+
"voice": 6,
|
|
50
51
|
"pass": 4,
|
|
51
52
|
"context": 3,
|
|
52
53
|
"agent": 2,
|
|
@@ -55,5 +56,5 @@
|
|
|
55
56
|
"media": 1
|
|
56
57
|
},
|
|
57
58
|
"pluginSkillCount": 245,
|
|
58
|
-
"toolkitVersion": "3.
|
|
59
|
+
"toolkitVersion": "3.16.0"
|
|
59
60
|
}
|
|
@@ -66,6 +66,7 @@ const DEV_ONLY_TOOLING = Object.freeze([
|
|
|
66
66
|
"lint-personas.mjs", // imports install/_codex-agents.mjs, which never ships to scripts/
|
|
67
67
|
"lint-mcp-refs.mjs",
|
|
68
68
|
"check-md-links.mjs",
|
|
69
|
+
"check-rewrite-overlap.mjs", // compares a rewrite against a source only a maintainer has
|
|
69
70
|
"gen-ref-toc.mjs", // regenerates the repo's own reference tree; an install has no pipeline/
|
|
70
71
|
"validate-schemas.mjs", // validates the repo's own schema files, needs ajv
|
|
71
72
|
"sync-parity-check.sh",
|
package/manifest.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "20.
|
|
4
|
-
"commit": "
|
|
5
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"version": "20.6.0",
|
|
4
|
+
"commit": "3fc39a82ab127eef5b3e5004ebc279232a2d020c",
|
|
5
|
+
"generatedAt": "2026-09-26T14:39:59.575Z",
|
|
6
6
|
"algorithm": "sha256",
|
|
7
|
-
"fileCount":
|
|
7
|
+
"fileCount": 1220,
|
|
8
8
|
"files": {
|
|
9
|
-
"CHANGELOG.md": "
|
|
10
|
-
"LICENSE": "
|
|
9
|
+
"CHANGELOG.md": "67d8197e2f23337512cca774519c4f111868adbf736f284c2919a49ae684b6c5",
|
|
10
|
+
"LICENSE": "ab7264a940291b3fd39e40133c06772351490f5e33c88e20fd5d8333227abfa0",
|
|
11
11
|
"README.md": "0613eebb0b38062bd94c44043595d1950b5c9b470874dd8e402c052fd20960cf",
|
|
12
12
|
"README.tr.md": "0c6b5e6943d6c3d3bf3a7ed99c705fb9a78d15d7722e6252f58404d3f6d3524d",
|
|
13
13
|
"SECURITY.md": "0eb89553ddcfc56bfe9712315b55a6630ee2f579631aaeccd6ad24f8ac769a0b",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"docs/best-practices.md": "f8b75b0d38b5896a6a852bb30a1edddeb24dab8b5b7b1d9313290e1241c72024",
|
|
34
34
|
"docs/ecosystem.md": "e6c00f30e95b5e178f77bdfbefd686c8a4164f39c03865a89ab23465d1a37acf",
|
|
35
35
|
"docs/engineering.md": "550528cd47c7d2c921729c68d0782a00ac8e77a4bdfec479f6749f9c3235d188",
|
|
36
|
-
"docs/facts.json": "
|
|
36
|
+
"docs/facts.json": "34a26f599d40a37e2bdd7f35b208ddb211c444b854e942adb88132c96f8ecba6",
|
|
37
37
|
"docs/features.md": "6c9f0efb00c683e6f611c440c87847b187f8b0eee59e9e812f3a241695bc2ae1",
|
|
38
38
|
"docs/performance.md": "bbf2f3922c25dcba0c1a9e69ef420e3871a729af5b35009a49b48a5d3c5cd151",
|
|
39
39
|
"docs/recovery-guide.md": "59a8f1b66b34cb9ff5507b3d54dac3b9915e184aa044d9c758d18cac818d510e",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"install/_codex-instructions.mjs": "add75ffc0e8d8db0e3bf275b46017a897a4cadb83dc66c7d3affd73bcead20f8",
|
|
45
45
|
"install/_common.mjs": "8e7dac0c08e0712b3fec5ce6b8db08ccfb4bd3e36d98d22d9e1df8dcd5371250",
|
|
46
46
|
"install/_copilot-instructions.mjs": "73e4b82b2af0f3e1669f772fa0788dac6b540c131e56d5fe38dcf29be85cc019",
|
|
47
|
-
"install/_dev-only-files.mjs": "
|
|
47
|
+
"install/_dev-only-files.mjs": "51285901f3b650ff22a76f8c05b23c69b9ceb6fe3da3333cece9e0caf65065a4",
|
|
48
48
|
"install/_managed-block.mjs": "7328728a7c82744b66218034389fc295d1384311daa5415408afa94be14303d3",
|
|
49
49
|
"install/_mcp-register.mjs": "56a785984afbba54aedc3677652ca4ee990d98d45b6599a3937cb656cad53b46",
|
|
50
50
|
"install/_platform-filter.mjs": "1c66acc79da0da0c1aa5a57050989e84198c3153e6437ee03f1d3bacbb12ff2c",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"install/templates/multi-agent-autopilot-awake.plist.template": "ddd25c76c56fcbb38a89de8e35637372d70ace993d6bc487a88ca9cfb702fe1f",
|
|
62
62
|
"install/templates/multi-agent-autopilot.plist.template": "122c8a61cd927edd99375d6e3da40f1d42bcceed5b6641785c8b28bb930bc77e",
|
|
63
63
|
"install/unattended-profile-legacy.json": "f52e86b998956f09ad306b1735a6c10b58580d7082d8a55665a4c6bb15c3aa1f",
|
|
64
|
-
"package.json": "
|
|
64
|
+
"package.json": "ad296bb61988074861178c88e988bfef7332fd592e7f6c88d51b25c81a51ae3d",
|
|
65
65
|
"pipeline/agents/android-architect.md": "11fb74543cb0ebcf9525ca6654fe95d53da7229e0bf4a1149b31fd9005abc453",
|
|
66
66
|
"pipeline/agents/backend-architect.md": "b02a606c301e4714131aed70ae6d16eeb77fbc819a001e2752cff2488d4246de",
|
|
67
67
|
"pipeline/agents/bulk-reader.md": "6597052cd7d205bac2b1c7633f5232fe1a40730664a913b8eb77efd5a786dc25",
|
|
@@ -167,6 +167,7 @@
|
|
|
167
167
|
"pipeline/lib/autopilot-state.sh": "e97d4cb4fce18be8be3485702be0e6d0948209a14f9725cbc83c2721d293432a",
|
|
168
168
|
"pipeline/lib/channels-multi-repo.sh": "aef26f888bddbaf7fb0e2e86143d612da776b4a4282bcee8144a090a9050b989",
|
|
169
169
|
"pipeline/lib/classify-intent.sh": "dab0896bc9707a10ac046d0b31d25a48c363cdde76cb1d720bf847deefc0364c",
|
|
170
|
+
"pipeline/lib/confusables.json": "2638872fc47c7a8c087902938d67a88440e855e66ede487ac1c057ff85f1571f",
|
|
170
171
|
"pipeline/lib/context-link-extractor.sh": "84a154dcabf57136be17199b21d02120d70cc0ca34e8f48fe24bdda74eddda1d",
|
|
171
172
|
"pipeline/lib/count-lib.sh": "7bafe152b0ffb9ca6fb9296b7ecaaef33deb9410286b3579fefbb37796044f79",
|
|
172
173
|
"pipeline/lib/credential-inventory.sh": "adff81b265a9cf4f8c9135011686c12963cf5eeeafcb266be54456c803ffde38",
|
|
@@ -192,13 +193,14 @@
|
|
|
192
193
|
"pipeline/lib/model-dispatch.sh": "7edd5c92d41ee3bf74972e0827ccbb671853d5410e7fb0af0b7cb020f95e05c5",
|
|
193
194
|
"pipeline/lib/model-rung.sh": "2a5e04af3c6829d4c72e5f518e668a9cc2feffe6f2abb90a68c485f52e81959a",
|
|
194
195
|
"pipeline/lib/multi-repo-pipeline.sh": "5cca6bcc4fb857448ddadb31add343952be89423a328267e56496c7b7cdc7710",
|
|
195
|
-
"pipeline/lib/
|
|
196
|
+
"pipeline/lib/normalize-text.mjs": "db5f3b6618c679762227083299ec7f7bef8f1bb6b9179f19449b5ff904812c45",
|
|
197
|
+
"pipeline/lib/outbound-gate.mjs": "4e89bf43554af4c727ef110505658159c7a7a3c53d8a4007f0e2e65dab87896f",
|
|
196
198
|
"pipeline/lib/parse-complaints.sh": "d787ce870f141cf7f881da80fab4320814044554065e82e94632f061675b4d12",
|
|
197
199
|
"pipeline/lib/phase-schema.mjs": "8fc0c909f129341295cd8ef2ea9e93cecd0ac493c3a8d83e25879f7ff522f1ab",
|
|
198
200
|
"pipeline/lib/plan-todos.sh": "4992bed180774a27f84892add8b09e0f7b53e9b8fefb75f60325a04d1af18c56",
|
|
199
201
|
"pipeline/lib/post-pr-review.sh": "c7a8f8c648ac7aebf904e70ae482c026de5b3cc897b376cd8aa52356fda8b805",
|
|
200
202
|
"pipeline/lib/pr-request-location.mjs": "622ca4705ec8ee00e0a4609657d8ee684fcdc077f3099ab312cf41b5b0c41efd",
|
|
201
|
-
"pipeline/lib/redact.mjs": "
|
|
203
|
+
"pipeline/lib/redact.mjs": "9abe7d74a1fa5d9d1ca9742652b485626e0ae86b62a7229c9ac478813b854bcf",
|
|
202
204
|
"pipeline/lib/regular-file.mjs": "084dd2c98a664a1e4a937757cc5e5e5d7e162b6aa422f8c7cfdb27f7834c6cbc",
|
|
203
205
|
"pipeline/lib/repo-cache.sh": "82bbdd3d61746042ad5341554a1077098d4b56f15caf6c5cc092377a273c8f45",
|
|
204
206
|
"pipeline/lib/repo-hygiene.sh": "67f4227d2bc84198d51205f1f16d055e76a07fe11a4e5c2b8103559c44cefa04",
|
|
@@ -217,7 +219,7 @@
|
|
|
217
219
|
"pipeline/lib/usage-endpoint.mjs": "9aed3a854e729f82563ca7614c9ab8dbb5af3b42fa0f7c10204b0094627b9906",
|
|
218
220
|
"pipeline/lib/user-facing.mjs": "08d8a1f4f12e36de88673d24167e244862f56671586a5ce26e73f52821996eed",
|
|
219
221
|
"pipeline/lib/user-facing.sh": "969038aa575a7075cbc8c2857b3e6bddfa5c8ea8a15811ad65c6660735539b76",
|
|
220
|
-
"pipeline/lib/vercel-deploy.sh": "
|
|
222
|
+
"pipeline/lib/vercel-deploy.sh": "50d3d8a0b2c60daf90365396f37337d89a60230a94e7bd4c89b19acf1849de99",
|
|
221
223
|
"pipeline/multi-agent-refs/_account-picker.md": "42715ba746e1fc681a57436d72fc643e3f16de023f0409fee8782b013f7a144a",
|
|
222
224
|
"pipeline/multi-agent-refs/_dev-context.md": "4d0905cc537c7f8526d3c92cf09bb44d2ab50680470ee0baa2dd5c3b1d0565d5",
|
|
223
225
|
"pipeline/multi-agent-refs/_input-parser.md": "4cf222b1c4a405bd71521f5290f45408cc8a47913a0a39166c68210104e90cc5",
|
|
@@ -427,7 +429,7 @@
|
|
|
427
429
|
"pipeline/scripts/_smoke-root.sh": "dad129959a4e37a0cd0f4f6c04f6d66c5a43752b2bf33989db9c002eb106b832",
|
|
428
430
|
"pipeline/scripts/_stack-adapter.mjs": "a754c042d237cd03abf11c6909099a7a3ea798cdd13d49b03787797fd3496cd7",
|
|
429
431
|
"pipeline/scripts/_stack-routing.mjs": "d64421b48eb42efffeae01f1d64ec56182269285de62e80434f3b8b05610e2ab",
|
|
430
|
-
"pipeline/scripts/agent-guard.py": "
|
|
432
|
+
"pipeline/scripts/agent-guard.py": "764e7ce3dd448b37cf7b69b03def5d173ab8dedadd2b2dca098e20ca15fd8604",
|
|
431
433
|
"pipeline/scripts/agent-guard.sh": "5b57cea687754533c673b66a9dd2281ca06d12ef86bdc0d03fa285fc9e7debf8",
|
|
432
434
|
"pipeline/scripts/aggregate-metrics.mjs": "c324e349993fb154a885263cf6665af0ff656e9c987b455a263dad8b96cca667",
|
|
433
435
|
"pipeline/scripts/analysis-story-tree.mjs": "8fd371d3e552a3f60533f18c7699074ae6089016c6663512410c4fa02494548f",
|
|
@@ -526,8 +528,8 @@
|
|
|
526
528
|
"pipeline/scripts/plan-critique-gate.mjs": "b139394e5f16d11e4997d0bb0b08529f439dbd7fad39781ef4c2475247d8fa97",
|
|
527
529
|
"pipeline/scripts/plugin-refresh.sh": "687a33199ad963616ab537e32210ec8c967e76ecc42f494ee8a1dc9fe947fc30",
|
|
528
530
|
"pipeline/scripts/pr-request.mjs": "e6f4ded2366c6599bb345b3011ecc771e13b8a1561afa4c9a50bca4edff17456",
|
|
529
|
-
"pipeline/scripts/pre-commit-check.sh": "
|
|
530
|
-
"pipeline/scripts/pre-push-check.sh": "
|
|
531
|
+
"pipeline/scripts/pre-commit-check.sh": "a4a091abff1f3a5695227bfca63cd257b09e859f8bd10bfba5700e0c9334e6ea",
|
|
532
|
+
"pipeline/scripts/pre-push-check.sh": "e449aff7f0336d7d624eff4c6221f56d8621ba9d010669601755e57fd4ee02c7",
|
|
531
533
|
"pipeline/scripts/probe-evidence-capability.sh": "ac74252eed6b773fdbc7ec475a63606925f1ba7b5e7c6f911329f46fd920eae7",
|
|
532
534
|
"pipeline/scripts/prune-logs.sh": "c5587b33104c7253e07be8cce8249b50014c52dfdf232de637811ffdd88e95e4",
|
|
533
535
|
"pipeline/scripts/purge.sh": "95c60b272ae47209893dba9b17528538592bb5636da47940a02734cd9b4f0ce5",
|
|
@@ -545,7 +547,7 @@
|
|
|
545
547
|
"pipeline/scripts/routine-registry.mjs": "eaffbb840cd0ac977d46659cb3bf5c6cbd21a969ee50491cdb4295d10d9d636b",
|
|
546
548
|
"pipeline/scripts/run-aggregator.mjs": "e58ecdf900aa30ea1625939d72e6d4e26d13ae4e6bb2ee1fd68d99718def27c8",
|
|
547
549
|
"pipeline/scripts/run-metrics.mjs": "6db53f3814a992c1f23beae7a54c84310be5af8e84dbff5a6768a91169152f02",
|
|
548
|
-
"pipeline/scripts/run-ui-tests.sh": "
|
|
550
|
+
"pipeline/scripts/run-ui-tests.sh": "11331115a4c3fb436f8f35894b7f12a1be317c7cfcd7b7512aeb42b6a9aa21e3",
|
|
549
551
|
"pipeline/scripts/runs-index.mjs": "7937dd3d6f3c5b7800d853ff06289fd5551413dda4f46b0f3db81483b7280e66",
|
|
550
552
|
"pipeline/scripts/scaffold-gate.mjs": "e2935957771f1d332b9968a819de49676152c51991e89f4a9cab7467b6d18701",
|
|
551
553
|
"pipeline/scripts/scan-agent-config.sh": "41dbd995de799f599dc365ea8cfe55542c48c7387c4f94a596a1e6feec278168",
|
|
@@ -592,12 +594,12 @@
|
|
|
592
594
|
"pipeline/scripts/verify-citations.mjs": "ed1c3d7d0dbebbc64a26524f0f321d3a0ec1318cbb60dd4517d6b3db77e7fd74",
|
|
593
595
|
"pipeline/scripts/verify-skills.sh": "871fcdfe5567232cd49a4cce4401911a06c8df6ab712d0597e17ce1bf0f63d80",
|
|
594
596
|
"pipeline/scripts/verify.mjs": "fdc9843dc33b37d08d132617f158b8fe10cc4ab3be5bf31714bf2453ac0f714f",
|
|
595
|
-
"pipeline/scripts/website-deploy-commit.sh": "
|
|
597
|
+
"pipeline/scripts/website-deploy-commit.sh": "73cae49db83cf98eeeb08eb8852a13912bd5f05855319b184e348b9ca4dff44f",
|
|
596
598
|
"pipeline/scripts/worktree-finalize.sh": "ca2bb363bec738cfce57ce5f46962d69664810003cc56dde0a82ab9133dbf557",
|
|
597
599
|
"pipeline/scripts/worktree-prepare.sh": "9391c828375b127f77c86e3c830dbbaf355a61464c6437319969f2f1eecc72b1",
|
|
598
600
|
"pipeline/scripts/worktrees.mjs": "90dfd09b70fffe9b56c331a4ae2692cc248f4898f4ee6aee4239ef0b20888a90",
|
|
599
601
|
"pipeline/scripts/write-state.mjs": "e1198bf970eace07d3eff22b394808a526e473df65d3d58aa2d5a12cfdac8b49",
|
|
600
|
-
"pipeline/skills/.skill-manifest.json": "
|
|
602
|
+
"pipeline/skills/.skill-manifest.json": "94bbe3de5468d7277f6268a570c82af7d8295166e2d9c27676839646c4faff77",
|
|
601
603
|
"pipeline/skills/.skills-index.json": "042b1090f3e2ca310c6ce70d1bb304e8c7f96bd73133e504b95d83801608e470",
|
|
602
604
|
"pipeline/skills/shared/README.md": "98196ea7c9d84637e8917ca4053c34b45de46586f71fb0c0034dce0808af857d",
|
|
603
605
|
"pipeline/skills/shared/core/apple-archive-compliance/SKILL.md": "7239fef717ea45b79276bfcc2c3903acd57950b6e00dd6a2a66399c4b09cbd51",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmerterden/multi-agent-pipeline",
|
|
3
|
-
"version": "20.
|
|
3
|
+
"version": "20.6.0",
|
|
4
4
|
"description": "6-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"start": "node index.js",
|
|
23
|
-
"test": "npm run format:check && npm run lint && node --test test/*.test.mjs && node pipeline/scripts/run-smokes.mjs && node pipeline/scripts/lint-skills.mjs && node pipeline/scripts/lint-personas.mjs && node pipeline/scripts/lint-mcp-refs.mjs && node pipeline/scripts/eval-triage.mjs && node pipeline/scripts/eval-golden-tasks.mjs && node pipeline/scripts/eval-intent.mjs && node pipeline/scripts/eval-recall.mjs && node pipeline/scripts/validate-schemas.mjs && node pipeline/scripts/validate-prefs.mjs && node pipeline/scripts/scorecard.mjs",
|
|
23
|
+
"test": "npm run format:check && npm run lint && node --test test/*.test.mjs && node pipeline/scripts/run-smokes.mjs && node pipeline/scripts/lint-skills.mjs && node pipeline/scripts/lint-personas.mjs && node pipeline/scripts/lint-mcp-refs.mjs && node pipeline/scripts/eval-triage.mjs && node pipeline/scripts/eval-golden-tasks.mjs && node pipeline/scripts/eval-intent.mjs && node pipeline/scripts/eval-recall.mjs && node pipeline/scripts/eval-guard-robustness.mjs && node pipeline/scripts/validate-schemas.mjs && node pipeline/scripts/validate-prefs.mjs && node pipeline/scripts/scorecard.mjs",
|
|
24
24
|
"test:unit": "node --test test/*.test.mjs",
|
|
25
25
|
"test:smoke": "node pipeline/scripts/run-smokes.mjs",
|
|
26
26
|
"lint:skills": "node pipeline/scripts/lint-skills.mjs",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"codex-cli"
|
|
56
56
|
],
|
|
57
57
|
"author": "Mert Erden",
|
|
58
|
-
"license": "MIT",
|
|
58
|
+
"license": "MIT AND LicenseRef-PolyForm-Perimeter-1.0.0",
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"registry": "https://registry.npmjs.org",
|
|
61
61
|
"access": "public"
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"!pipeline/scripts/smoke-*.sh",
|
|
94
94
|
"!pipeline/scripts/run-smokes.mjs",
|
|
95
95
|
"!pipeline/scripts/eval-*.mjs",
|
|
96
|
+
"!pipeline/scripts/check-rewrite-overlap.mjs",
|
|
96
97
|
"!pipeline/scripts/scorecard.mjs",
|
|
97
98
|
"!pipeline/scripts/lint-skills.mjs",
|
|
98
99
|
"!pipeline/scripts/lint-personas.mjs",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_readme": "Characters a guard folds before matching, applied after NFKC. `strip` lists invisible characters that are removed. `map` sends a character that renders like an ASCII one to that ASCII character: the dash family, and the Cyrillic and Greek letters whose glyphs match Latin ones. Chosen from the Unicode confusables data for the letters and punctuation the guard rules use. Read by lib/normalize-text.mjs and scripts/agent-guard.py.",
|
|
3
|
+
"strip": [
|
|
4
|
+
"\u00ad",
|
|
5
|
+
"\u034f",
|
|
6
|
+
"\u061c",
|
|
7
|
+
"\u115f",
|
|
8
|
+
"\u1160",
|
|
9
|
+
"\u17b4",
|
|
10
|
+
"\u17b5",
|
|
11
|
+
"\u180e",
|
|
12
|
+
"\u200b",
|
|
13
|
+
"\u200c",
|
|
14
|
+
"\u200d",
|
|
15
|
+
"\u200e",
|
|
16
|
+
"\u200f",
|
|
17
|
+
"\u202a",
|
|
18
|
+
"\u202b",
|
|
19
|
+
"\u202c",
|
|
20
|
+
"\u202d",
|
|
21
|
+
"\u202e",
|
|
22
|
+
"\u2060",
|
|
23
|
+
"\u2061",
|
|
24
|
+
"\u2062",
|
|
25
|
+
"\u2063",
|
|
26
|
+
"\u2064",
|
|
27
|
+
"\u2066",
|
|
28
|
+
"\u2067",
|
|
29
|
+
"\u2068",
|
|
30
|
+
"\u2069",
|
|
31
|
+
"\u3164",
|
|
32
|
+
"\ufeff",
|
|
33
|
+
"\uffa0"
|
|
34
|
+
],
|
|
35
|
+
"map": {
|
|
36
|
+
"\u2010": "-",
|
|
37
|
+
"\u2011": "-",
|
|
38
|
+
"\u2012": "-",
|
|
39
|
+
"\u2013": "-",
|
|
40
|
+
"\u2014": "-",
|
|
41
|
+
"\u2015": "-",
|
|
42
|
+
"\u2043": "-",
|
|
43
|
+
"\u2212": "-",
|
|
44
|
+
"\ufe58": "-",
|
|
45
|
+
"\ufe63": "-",
|
|
46
|
+
"\u02d0": ":",
|
|
47
|
+
"\u0589": ":",
|
|
48
|
+
"\u05c3": ":",
|
|
49
|
+
"\u2236": ":",
|
|
50
|
+
"\ua789": ":",
|
|
51
|
+
"\u0410": "A",
|
|
52
|
+
"\u0412": "B",
|
|
53
|
+
"\u0421": "C",
|
|
54
|
+
"\u0415": "E",
|
|
55
|
+
"\u041d": "H",
|
|
56
|
+
"\u0406": "I",
|
|
57
|
+
"\u0408": "J",
|
|
58
|
+
"\u041a": "K",
|
|
59
|
+
"\u041c": "M",
|
|
60
|
+
"\u041e": "O",
|
|
61
|
+
"\u0420": "P",
|
|
62
|
+
"\u0405": "S",
|
|
63
|
+
"\u0422": "T",
|
|
64
|
+
"\u0425": "X",
|
|
65
|
+
"\u0423": "Y",
|
|
66
|
+
"\u0430": "a",
|
|
67
|
+
"\u0441": "c",
|
|
68
|
+
"\u0435": "e",
|
|
69
|
+
"\u0456": "i",
|
|
70
|
+
"\u0458": "j",
|
|
71
|
+
"\u043e": "o",
|
|
72
|
+
"\u0440": "p",
|
|
73
|
+
"\u0455": "s",
|
|
74
|
+
"\u0445": "x",
|
|
75
|
+
"\u0443": "y",
|
|
76
|
+
"\u04bb": "h",
|
|
77
|
+
"\u0501": "d",
|
|
78
|
+
"\u051b": "q",
|
|
79
|
+
"\u051d": "w",
|
|
80
|
+
"\u0391": "A",
|
|
81
|
+
"\u0392": "B",
|
|
82
|
+
"\u0395": "E",
|
|
83
|
+
"\u0396": "Z",
|
|
84
|
+
"\u0397": "H",
|
|
85
|
+
"\u0399": "I",
|
|
86
|
+
"\u039a": "K",
|
|
87
|
+
"\u039c": "M",
|
|
88
|
+
"\u039d": "N",
|
|
89
|
+
"\u039f": "O",
|
|
90
|
+
"\u03a1": "P",
|
|
91
|
+
"\u03a4": "T",
|
|
92
|
+
"\u03a5": "Y",
|
|
93
|
+
"\u03a7": "X",
|
|
94
|
+
"\u03bf": "o",
|
|
95
|
+
"\u03b1": "a",
|
|
96
|
+
"\u03bd": "v",
|
|
97
|
+
"\u03c1": "p",
|
|
98
|
+
"\u0131": "i"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* normalize-text.mjs - fold look-alike text to the form a guard matches on.
|
|
4
|
+
*
|
|
5
|
+
* NFKC, then the invisible characters in confusables.json removed and its
|
|
6
|
+
* look-alike letters and punctuation mapped to ASCII. A guard matches on the
|
|
7
|
+
* raw text and on this folded copy, so a trailer or a token written with a
|
|
8
|
+
* zero-width space or a Cyrillic letter is still recognised. The folded copy is
|
|
9
|
+
* only ever matched against; nothing written or shown uses it.
|
|
10
|
+
*
|
|
11
|
+
* As a CLI it reads stdin and writes the folded text to stdout.
|
|
12
|
+
*
|
|
13
|
+
* @module pipeline/lib/normalize-text
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { invokedDirectly } from "./invoked-directly.mjs";
|
|
19
|
+
|
|
20
|
+
const TABLE = JSON.parse(readFileSync(join(import.meta.dirname, "confusables.json"), "utf8"));
|
|
21
|
+
const STRIP = new Set(TABLE.strip);
|
|
22
|
+
const MAP = new Map(Object.entries(TABLE.map));
|
|
23
|
+
|
|
24
|
+
/** @param {string} text */
|
|
25
|
+
export function normalizeForMatch(text) {
|
|
26
|
+
if (typeof text !== "string" || !text) return text;
|
|
27
|
+
let out = "";
|
|
28
|
+
for (const ch of text.normalize("NFKC")) {
|
|
29
|
+
if (STRIP.has(ch)) continue;
|
|
30
|
+
out += MAP.get(ch) ?? ch;
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The text with its invisible characters removed and nothing else changed. */
|
|
36
|
+
export function stripInvisible(text) {
|
|
37
|
+
if (typeof text !== "string" || !text) return text;
|
|
38
|
+
let out = "";
|
|
39
|
+
for (const ch of text) if (!STRIP.has(ch)) out += ch;
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (invokedDirectly(import.meta.url)) {
|
|
44
|
+
let input = "";
|
|
45
|
+
process.stdin.setEncoding("utf8");
|
|
46
|
+
process.stdin.on("data", (d) => (input += d));
|
|
47
|
+
process.stdin.on("end", () => process.stdout.write(normalizeForMatch(input)));
|
|
48
|
+
}
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
|
|
50
50
|
import { readFileSync } from "node:fs";
|
|
51
51
|
import { invokedDirectly } from "./invoked-directly.mjs";
|
|
52
|
+
import { normalizeForMatch } from "./normalize-text.mjs";
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
55
|
* Each rule is a SHAPE, not a dictionary. A list of known token prefixes goes
|
|
@@ -155,11 +156,19 @@ export function scan(text) {
|
|
|
155
156
|
const findings = [];
|
|
156
157
|
const lines = String(text).split("\n");
|
|
157
158
|
for (let i = 0; i < lines.length; i++) {
|
|
159
|
+
// A credential written with an invisible character or a look-alike letter
|
|
160
|
+
// still matches: the certain rules also run on the folded line.
|
|
161
|
+
const folded = normalizeForMatch(lines[i]);
|
|
158
162
|
for (const rule of RULES) {
|
|
159
163
|
// `lastIndex` survives between calls on a /g regex and would make every
|
|
160
164
|
// second line skip its start. Reset per line.
|
|
161
165
|
rule.re.lastIndex = 0;
|
|
162
|
-
|
|
166
|
+
let hit = rule.re.test(lines[i]);
|
|
167
|
+
if (!hit && rule.certain && folded !== lines[i]) {
|
|
168
|
+
rule.re.lastIndex = 0;
|
|
169
|
+
hit = rule.re.test(folded);
|
|
170
|
+
}
|
|
171
|
+
if (hit) {
|
|
163
172
|
findings.push({ rule: rule.name, certain: rule.certain, line: i + 1 });
|
|
164
173
|
}
|
|
165
174
|
}
|
package/pipeline/lib/redact.mjs
CHANGED
|
@@ -15,13 +15,16 @@
|
|
|
15
15
|
|
|
16
16
|
import { RULES } from "./outbound-gate.mjs";
|
|
17
17
|
import { invokedDirectly } from "./invoked-directly.mjs";
|
|
18
|
+
import { stripInvisible } from "./normalize-text.mjs";
|
|
18
19
|
|
|
19
20
|
const CERTAIN = RULES.filter((r) => r.certain);
|
|
20
21
|
|
|
21
22
|
/** @param {string} text */
|
|
22
23
|
export function redact(text) {
|
|
23
24
|
if (typeof text !== "string" || !text) return text;
|
|
24
|
-
|
|
25
|
+
// Invisible characters are removed first: they change nothing a reader sees,
|
|
26
|
+
// and a token split by one would otherwise slip past every rule.
|
|
27
|
+
let out = stripInvisible(text);
|
|
25
28
|
for (const rule of CERTAIN) {
|
|
26
29
|
const re = new RegExp(
|
|
27
30
|
rule.re.source,
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
#
|
|
3
3
|
# vercel-deploy.sh - safe wrapper around `vercel` CLI.
|
|
4
4
|
#
|
|
5
|
-
# Why this exists
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# leaked the deploy token into the conversation transcript and forced a
|
|
9
|
-
# token rotation. See `feedback_vercel_cli_token_leak.md`.
|
|
5
|
+
# Why this exists: when a `vercel` call fails, the CLI's retry hint prints the
|
|
6
|
+
# failed argv verbatim. A token passed as `--token=vcp_...` therefore reaches the
|
|
7
|
+
# terminal, any log, and the conversation transcript, and has to be rotated.
|
|
10
8
|
#
|
|
11
9
|
# This wrapper guarantees three properties for every Vercel call:
|
|
12
10
|
# 1. Tokens are passed via VERCEL_TOKEN env var, NEVER via --token=... argv.
|
|
@@ -186,8 +184,8 @@ cmd_deploy() {
|
|
|
186
184
|
done
|
|
187
185
|
|
|
188
186
|
# Run the CLI with token via env. Pipe stdout AND stderr through the redact
|
|
189
|
-
# filter so even if the CLI dumps argv on failure (
|
|
190
|
-
#
|
|
187
|
+
# filter so even if the CLI dumps argv on failure (its retry hint does),
|
|
188
|
+
# tokens never reach the user's screen or transcript.
|
|
191
189
|
set +e
|
|
192
190
|
( cd "$cwd" && VERCEL_TOKEN="$VERCEL_TOKEN" vercel "${cmd_args[@]}" 2>&1 ) \
|
|
193
191
|
| redact_filter
|
|
@@ -20,6 +20,7 @@ import re
|
|
|
20
20
|
import shlex
|
|
21
21
|
import signal
|
|
22
22
|
import sys
|
|
23
|
+
import unicodedata
|
|
23
24
|
|
|
24
25
|
# An internal deadline well under the hook timeout: if the guard cannot decide
|
|
25
26
|
# in this long, it prints the blocking verdict when unattended (fail-closed) and
|
|
@@ -48,6 +49,34 @@ ATTRIBUTION = re.compile(
|
|
|
48
49
|
r"|claude code <",
|
|
49
50
|
re.IGNORECASE,
|
|
50
51
|
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _load_confusables():
|
|
55
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
56
|
+
try:
|
|
57
|
+
with open(os.path.join(here, "..", "lib", "confusables.json"), encoding="utf-8") as fh:
|
|
58
|
+
table = json.load(fh)
|
|
59
|
+
return set(table.get("strip", [])), dict(table.get("map", {}))
|
|
60
|
+
except (OSError, ValueError):
|
|
61
|
+
return set(), {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
_STRIP, _MAP = _load_confusables()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def normalize_for_match(text: str) -> str:
|
|
68
|
+
"""NFKC, invisible characters removed, look-alike letters mapped to ASCII.
|
|
69
|
+
|
|
70
|
+
The same fold lib/normalize-text.mjs applies, from the same table. Rules
|
|
71
|
+
that match text content run on the raw command and on this copy, so a
|
|
72
|
+
trailer written with a zero-width space or a Cyrillic letter still matches.
|
|
73
|
+
Without the table only NFKC applies.
|
|
74
|
+
"""
|
|
75
|
+
return "".join(
|
|
76
|
+
_MAP.get(ch, ch) for ch in unicodedata.normalize("NFKC", text) if ch not in _STRIP
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
51
80
|
# Force flags, and ONLY force flags.
|
|
52
81
|
#
|
|
53
82
|
# The previous pattern ended in `-\S*f\S*`, which matches any flag containing an
|
|
@@ -228,7 +257,7 @@ def decide(cmd: str, depth: int = 0) -> str:
|
|
|
228
257
|
commits = re.search(r"git\s+commit", low) or any(
|
|
229
258
|
git_subcommand(toks) == "commit" for _, toks in segs
|
|
230
259
|
)
|
|
231
|
-
if commits and ATTRIBUTION.search(cmd):
|
|
260
|
+
if commits and (ATTRIBUTION.search(cmd) or ATTRIBUTION.search(normalize_for_match(cmd))):
|
|
232
261
|
return "BLOCK_ATTRIB"
|
|
233
262
|
|
|
234
263
|
# Rule 2: force-push to a protected branch. Evaluate each push segment on its
|
|
@@ -174,6 +174,19 @@ scan_file() {
|
|
|
174
174
|
|
|
175
175
|
[ -z "$content" ] && return 0
|
|
176
176
|
|
|
177
|
+
# A token written with an invisible character or a look-alike letter: scan a
|
|
178
|
+
# folded copy too. Only non-ASCII content pays for the node call.
|
|
179
|
+
if printf '%s' "$content" | LC_ALL=C grep -q '[^ -~[:space:]]' 2>/dev/null \
|
|
180
|
+
&& command -v node >/dev/null 2>&1 \
|
|
181
|
+
&& [ -f "$PRE_COMMIT_DIR/../lib/normalize-text.mjs" ]; then
|
|
182
|
+
local folded
|
|
183
|
+
folded=$(printf '%s' "$content" | node "$PRE_COMMIT_DIR/../lib/normalize-text.mjs" 2>/dev/null) || folded=""
|
|
184
|
+
if [ -n "$folded" ] && [ "$folded" != "$content" ]; then
|
|
185
|
+
content="$content
|
|
186
|
+
$folded"
|
|
187
|
+
fi
|
|
188
|
+
fi
|
|
189
|
+
|
|
177
190
|
# API keys / tokens (key = "long_string" pattern)
|
|
178
191
|
if echo "$content" | grep -qiE '(api[_-]?key|api[_-]?secret|access[_-]?token|auth[_-]?token|secret[_-]?key)\s*[:=]\s*["'"'"'][A-Za-z0-9+/=_-]{20,}'; then
|
|
179
192
|
echo "BLOCKED: Possible API key/token in $file" >&2
|
|
@@ -261,28 +274,58 @@ scan_file() {
|
|
|
261
274
|
# repo-root-relative, so the full path can be anchored.
|
|
262
275
|
if ! echo "$file" | grep -qiE '(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|Podfile\.lock|Cartfile\.resolved|Package\.resolved|\.lock$|\.map$|__snapshots__|\.snap$|\.min\.(js|css)$)' \
|
|
263
276
|
&& ! echo "$file" | grep -qE '^pipeline/scripts/smoke-pre-commit\.sh$'; then
|
|
264
|
-
|
|
277
|
+
# Staged and working-tree content arrives as diff lines, so the leading "+"
|
|
278
|
+
# is stripped first: it is in the candidate class and would otherwise glue
|
|
279
|
+
# onto a token that starts at column 0 and defeat every shape check below.
|
|
280
|
+
candidates=$(echo "$content" | sed 's/^+//' | grep -oE '[A-Za-z0-9+/=_-]{40,}' || true)
|
|
265
281
|
if [ -n "$candidates" ]; then
|
|
266
282
|
entropy_hit=$(printf '%s\n' "$candidates" | awk '
|
|
283
|
+
# Word-shaped identifier: letters and _ or - only, split into segments at
|
|
284
|
+
# separators and case boundaries (UIApplication -> UI, Application), where
|
|
285
|
+
# the segments read like words: average length 4+, none longer than 24,
|
|
286
|
+
# and at least 70 percent carrying a vowel. Random letters-only tokens
|
|
287
|
+
# flip case every one or two characters, so their segments average about
|
|
288
|
+
# two characters with few vowels; a long lowercase run is one oversized
|
|
289
|
+
# segment. Neither shape passes.
|
|
290
|
+
function wordlike(t, n, i, c, p, nx, seg, nseg, total, maxlen, voweled, split_here) {
|
|
291
|
+
if (t !~ /^[A-Za-z_-]+$/) return 0;
|
|
292
|
+
n = length(t); seg = ""; nseg = 0; total = 0; maxlen = 0; voweled = 0;
|
|
293
|
+
for (i = 1; i <= n + 1; i++) {
|
|
294
|
+
c = (i <= n) ? substr(t, i, 1) : "_";
|
|
295
|
+
split_here = 0;
|
|
296
|
+
if (c == "_" || c == "-") split_here = 1;
|
|
297
|
+
else if (seg != "" && c ~ /[A-Z]/) {
|
|
298
|
+
p = substr(seg, length(seg), 1);
|
|
299
|
+
nx = (i < n) ? substr(t, i + 1, 1) : "";
|
|
300
|
+
if (p ~ /[a-z]/ || (nx ~ /[a-z]/ && length(seg) > 1)) split_here = 1;
|
|
301
|
+
}
|
|
302
|
+
if (split_here && seg != "") {
|
|
303
|
+
nseg++; total += length(seg);
|
|
304
|
+
if (length(seg) > maxlen) maxlen = length(seg);
|
|
305
|
+
if (tolower(seg) ~ /[aeiouy]/) voweled++;
|
|
306
|
+
seg = "";
|
|
307
|
+
}
|
|
308
|
+
if (c != "_" && c != "-") seg = seg c;
|
|
309
|
+
}
|
|
310
|
+
if (nseg == 0) return 0;
|
|
311
|
+
return (total / nseg >= 4 && maxlen <= 24 && voweled * 10 >= nseg * 7);
|
|
312
|
+
}
|
|
267
313
|
{
|
|
268
314
|
s=$0;
|
|
269
315
|
# Scoring the joined string measures the variety of a whole SENTENCE
|
|
270
316
|
# rather than of any token in it, so an ordinary doc path like
|
|
271
|
-
# "claude/commands/multi-agent/review-jira/SKILL" scores 4.35 and
|
|
272
|
-
# blocked as a possible credential
|
|
273
|
-
#
|
|
274
|
-
# the gate only fired once such a file was staged, which is why it
|
|
275
|
-
# stayed invisible.
|
|
317
|
+
# "claude/commands/multi-agent/review-jira/SKILL" scores 4.35 and would
|
|
318
|
+
# be blocked as a possible credential, making every doc that cites
|
|
319
|
+
# such a path un-editable.
|
|
276
320
|
#
|
|
277
|
-
# The reduction is applied ONLY to path-shaped candidates.
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
# strings in comments too, and it is right to.
|
|
321
|
+
# The reduction is applied ONLY to path-shaped candidates. Scoring the
|
|
322
|
+
# longest slash-free segment for every candidate containing a slash
|
|
323
|
+
# would structurally exempt any secret with a slash in it: the
|
|
324
|
+
# canonical 40-char AWS documentation-example secret access key (two
|
|
325
|
+
# slashes, full-match H=4.663) reduces to an 18-char segment, and
|
|
326
|
+
# roughly half of `openssl rand -base64 32` outputs contain a "/". The
|
|
327
|
+
# key is not reproduced here on purpose - this gate flags
|
|
328
|
+
# credential-shaped strings in comments too, and it is right to.
|
|
286
329
|
# No other check here catches that key: the key-name regex above does
|
|
287
330
|
# not match "secret_access_key", and the AKIA pattern only covers the
|
|
288
331
|
# key ID, not the secret.
|
|
@@ -299,7 +342,7 @@ scan_file() {
|
|
|
299
342
|
if (s ~ /^[0-9]+$/) next; # pure digits
|
|
300
343
|
if (s ~ /^[0-9a-f]+$/) next; # lowercase hex (git sha / md5 / sha-*)
|
|
301
344
|
if (s ~ /^[0-9A-F]+$/) next; # uppercase hex
|
|
302
|
-
if (s
|
|
345
|
+
if (wordlike(s)) next; # identifier made of words (plist key / type name), never a credential
|
|
303
346
|
delete freq;
|
|
304
347
|
for (i = 1; i <= n; i++) { c = substr(s, i, 1); freq[c]++ }
|
|
305
348
|
H = 0;
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
#
|
|
22
22
|
# Two modes, because a gate that runs INSIDE the push is a gate that breaks
|
|
23
23
|
# pushes. Git opens the connection to the remote before this hook fires, so a
|
|
24
|
-
# six-minute run idles that connection until the server drops it
|
|
25
|
-
#
|
|
26
|
-
#
|
|
24
|
+
# six-minute run idles that connection until the server drops it, and the push
|
|
25
|
+
# dies on a broken pipe with every gate green. The work has to happen outside
|
|
26
|
+
# the network window.
|
|
27
27
|
#
|
|
28
28
|
# pre-push-check.sh --run run every gate and stamp the tree as green
|
|
29
29
|
# pre-push-check.sh verify the stamp only; refuse the push if absent
|
|
@@ -277,11 +277,11 @@ EOF
|
|
|
277
277
|
fi
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
#
|
|
280
|
+
# Without a web arm, a repo with a full Playwright suite reports "no UI test
|
|
281
|
+
# target" and every downstream consumer believes it: the evidence probe closes
|
|
282
|
+
# tier 1, the PR body says UI tests were not run, and the reason it gives is
|
|
283
|
+
# true of the runner rather than of the repo. That is the failure mode this file
|
|
284
|
+
# exists to avoid.
|
|
285
285
|
web_spec_files() {
|
|
286
286
|
find "$REPO" \
|
|
287
287
|
-name ".*" -type d -prune -o \
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
# refuses to build a commit whose author is not a contributor on the project. The
|
|
8
8
|
# push still succeeds, the deployment is created and sits at UNKNOWN with a 0ms
|
|
9
9
|
# build (API `readyState: BLOCKED`), and the live site keeps serving the previous
|
|
10
|
-
# version.
|
|
11
|
-
#
|
|
10
|
+
# version. A successful push is therefore not evidence of a deploy, so the script
|
|
11
|
+
# pins the author before committing and reads the deployment state back after
|
|
12
|
+
# pushing instead of reporting the website as synced on the push alone.
|
|
12
13
|
#
|
|
13
14
|
# Usage:
|
|
14
15
|
# website-deploy-commit.sh <identity-name> <identity-email> <version> [repo-dir]
|