@massu/core 2.5.2 → 2.5.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/CHANGELOG.md +70 -0
- package/agents/massu-output-scorer.md +2 -2
- package/agents/massu-plan-auditor.md +1 -1
- package/commands/_shared-references/test-first-protocol.md +4 -4
- package/commands/_shared-references/verification-table.md +5 -5
- package/commands/massu-audit-deps.md +2 -2
- package/commands/massu-batch.md +3 -3
- package/commands/massu-checkpoint.md +7 -7
- package/commands/massu-ci-fix.md +1 -1
- package/commands/massu-cleanup.md +16 -16
- package/commands/massu-commit.md +7 -7
- package/commands/massu-create-plan.md +7 -7
- package/commands/massu-dead-code.md +3 -3
- package/commands/massu-debug/references/common-shortcuts.md +1 -1
- package/commands/massu-debug/references/investigation-phases.md +4 -4
- package/commands/massu-debug/references/report-format.md +2 -2
- package/commands/massu-deploy.md +1 -1
- package/commands/massu-deps.md +5 -5
- package/commands/massu-doc-gen.md +3 -3
- package/commands/massu-docs.md +6 -6
- package/commands/massu-estimate.md +3 -3
- package/commands/massu-golden-path/references/competitive-mode.md +2 -2
- package/commands/massu-golden-path/references/phase-1-plan-creation.md +1 -1
- package/commands/massu-golden-path/references/phase-2-implementation.md +2 -2
- package/commands/massu-golden-path/references/phase-4-commit.md +3 -3
- package/commands/massu-golden-path/references/phase-5-push.md +5 -5
- package/commands/massu-guide.md +3 -3
- package/commands/massu-hotfix.md +14 -14
- package/commands/massu-loop/references/checkpoint-audit.md +3 -3
- package/commands/massu-loop/references/iteration-structure.md +3 -3
- package/commands/massu-loop/references/loop-controller.md +5 -5
- package/commands/massu-loop/references/plan-extraction.md +4 -4
- package/commands/massu-loop/references/vr-plan-spec.md +3 -3
- package/commands/massu-loop-playwright.md +5 -5
- package/commands/massu-loop.md +1 -1
- package/commands/massu-new-feature.md +6 -24
- package/commands/massu-perf.md +1 -1
- package/commands/massu-plan.md +21 -21
- package/commands/massu-push-light.md +9 -9
- package/commands/massu-recap.md +1 -1
- package/commands/massu-refactor.md +8 -8
- package/commands/massu-release.md +3 -3
- package/commands/massu-review.md +1 -1
- package/commands/massu-rollback.md +6 -6
- package/commands/massu-scaffold-page.md +1 -1
- package/commands/massu-scaffold-router.md +1 -1
- package/commands/massu-status.md +9 -9
- package/commands/massu-tdd.md +2 -2
- package/commands/massu-test.md +13 -13
- package/commands/massu-verify-playwright.md +1 -1
- package/commands/massu-verify.md +20 -20
- package/dist/cli.js +1265 -876
- package/dist/hooks/auto-learning-pipeline.js +517 -56
- package/dist/hooks/classify-failure.js +495 -47
- package/dist/hooks/cost-tracker.js +65 -27
- package/dist/hooks/fix-detector.js +496 -47
- package/dist/hooks/incident-pipeline.js +72 -28
- package/dist/hooks/intent-suggester.js +65 -27
- package/dist/hooks/memory-recall.js +65 -27
- package/dist/hooks/post-edit-context.js +65 -27
- package/dist/hooks/post-tool-use.js +70 -29
- package/dist/hooks/pre-compact.js +65 -27
- package/dist/hooks/pre-delete-check.js +65 -27
- package/dist/hooks/pre-tool-use-gate.js +193 -37
- package/dist/hooks/quality-event.js +65 -27
- package/dist/hooks/rule-enforcement-pipeline.js +65 -27
- package/dist/hooks/security-gate.js +197 -38
- package/dist/hooks/session-end.js +68 -27
- package/dist/hooks/session-start.js +116 -61
- package/dist/hooks/user-prompt.js +65 -27
- package/fragments/feature-components.md +15 -0
- package/fragments/feature-components.python.md +15 -0
- package/fragments/feature-components.typescript.md +15 -0
- package/package.json +6 -5
- package/patterns/testing-patterns.md +8 -8
- package/protocols/plan-implementation.md +2 -2
- package/protocols/verification.md +4 -4
- package/reference/lessons-learned.md +4 -4
- package/reference/vr-verification-reference.md +9 -9
- package/templates/aspnet/massu.config.yaml +2 -0
- package/templates/go-chi/massu.config.yaml +2 -0
- package/templates/multi-runtime/massu.config.yaml +4 -2
- package/templates/phoenix/massu.config.yaml +2 -0
- package/templates/python-django/massu.config.yaml +3 -1
- package/templates/python-fastapi/massu.config.yaml +3 -1
- package/templates/python-flask/massu.config.yaml +3 -1
- package/templates/rails/massu.config.yaml +4 -2
- package/templates/rust-actix/massu.config.yaml +2 -0
- package/templates/spring/massu.config.yaml +2 -0
- package/templates/swift-ios/massu.config.yaml +3 -1
- package/templates/ts-nestjs/massu.config.yaml +2 -0
- package/templates/ts-nextjs/massu.config.yaml +2 -0
|
@@ -42,6 +42,15 @@ var init_memory_path = __esm({
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
// src/lib/corrections-md.ts
|
|
46
|
+
var CORRECTIONS_FILENAME;
|
|
47
|
+
var init_corrections_md = __esm({
|
|
48
|
+
"src/lib/corrections-md.ts"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
CORRECTIONS_FILENAME = "corrections.md";
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
45
54
|
// src/credentials.ts
|
|
46
55
|
import { homedir } from "os";
|
|
47
56
|
import { resolve } from "path";
|
|
@@ -143,7 +152,7 @@ var init_config_memory_schema = __esm({
|
|
|
143
152
|
message: "memory.recall.embedEndpoint must be an http(s) URL"
|
|
144
153
|
}).optional(),
|
|
145
154
|
embedModel: z.string().min(1).optional()
|
|
146
|
-
}).
|
|
155
|
+
}).prefault({}),
|
|
147
156
|
// --- Contradiction / supersede gate (plan-living-memory-slice-2-temporal-model, P5-001) ---
|
|
148
157
|
// When a new high-value memory (decision/correction) is written, find
|
|
149
158
|
// semantically-related existing records and, if the new one contradicts an
|
|
@@ -172,7 +181,7 @@ var init_config_memory_schema = __esm({
|
|
|
172
181
|
annotateSuperseded: z.boolean().default(false),
|
|
173
182
|
// Time budget (ms) for the contradiction check; exceeded → fail-open (ADD).
|
|
174
183
|
budgetMs: z.number().int().positive().default(800)
|
|
175
|
-
}).
|
|
184
|
+
}).prefault({}),
|
|
176
185
|
// --- Background consolidation, the "sleep-time" pass
|
|
177
186
|
// (plan-living-memory-slice-3-consolidation) ---
|
|
178
187
|
// Keeps memory sharp over years: dedupes, distills dying sessions into
|
|
@@ -218,7 +227,7 @@ var init_config_memory_schema = __esm({
|
|
|
218
227
|
// Surface optional upgrades (e.g. "a local model was detected") in chat.
|
|
219
228
|
suggestUpgrades: z.boolean().default(true),
|
|
220
229
|
suggestIntervalDays: z.number().int().positive().default(30)
|
|
221
|
-
}).
|
|
230
|
+
}).prefault({}),
|
|
222
231
|
// A-20 (Slice 4) — memory FILES: the mirror, and the one switch that decides whether
|
|
223
232
|
// Massu may ever WRITE into the user's memory directory.
|
|
224
233
|
//
|
|
@@ -243,7 +252,7 @@ var init_config_memory_schema = __esm({
|
|
|
243
252
|
// turn of EVERY session, so an unbounded index is a permanent context tax — and the
|
|
244
253
|
// per-session cap bounds only the RATE, never the total.
|
|
245
254
|
indexMaxLines: z.number().int().min(1).default(50)
|
|
246
|
-
}).
|
|
255
|
+
}).prefault({}),
|
|
247
256
|
// --- Cross-repo memory surfacing (plan-living-memory-slice-5-cross-repo-surfacing) ---
|
|
248
257
|
// A decision made in one of your repos can surface in another — opt-in per
|
|
249
258
|
// decision AND opt-in per repo, signed, verified, and materialized ONLY on
|
|
@@ -268,8 +277,8 @@ var init_config_memory_schema = __esm({
|
|
|
268
277
|
enabled: z.boolean().default(true),
|
|
269
278
|
maxCrossRepoItems: z.number().int().min(0).default(1),
|
|
270
279
|
minScore: z.number().optional()
|
|
271
|
-
}).
|
|
272
|
-
}).
|
|
280
|
+
}).prefault({})
|
|
281
|
+
}).prefault({})
|
|
273
282
|
}).optional();
|
|
274
283
|
}
|
|
275
284
|
});
|
|
@@ -449,13 +458,19 @@ function getResolvedPaths() {
|
|
|
449
458
|
settingsLocalPath: resolve2(root, claudeDirName, "settings.local.json")
|
|
450
459
|
};
|
|
451
460
|
}
|
|
452
|
-
var DomainConfigSchema, PatternRuleConfigSchema, CostModelSchema, AnalyticsConfigSchema, CustomPatternSchema, GovernanceConfigSchema, SecurityPatternSchema, SecurityConfigSchema, TeamConfigSchema, RegressionConfigSchema, AutoLearningConfigSchema, CloudConfigSchema, ConventionsConfigSchema, PythonDomainConfigSchema, PythonConfigSchema, PathsConfigSchema, LanguageFrameworkEntrySchema, FrameworkConfigSchema, DetectedConfigSchema, VerificationEntrySchema, VerificationConfigSchema, CanonicalPathsSchema, VerificationTypesSchema, DetectionRuleEntrySchema, DetectionConfigSchema, WatchConfigSchema, AdapterLocalPathSchema, AdaptersConfigSchema, TelemetryConfigSchema, LSPConfigSchema, RawConfigSchema, _config, _resolvedApiKeySource, _projectRoot;
|
|
461
|
+
var DEFAULT_KNOWLEDGE_SOURCE_FILES, DomainConfigSchema, PatternRuleConfigSchema, CostModelSchema, AnalyticsConfigSchema, CustomPatternSchema, GovernanceConfigSchema, SecurityPatternSchema, SecurityConfigSchema, TeamConfigSchema, RegressionConfigSchema, AutoLearningConfigSchema, CloudConfigSchema, ConventionsConfigSchema, PythonDomainConfigSchema, PythonConfigSchema, PathsConfigSchema, LanguageFrameworkEntrySchema, FrameworkConfigSchema, DetectedConfigSchema, VerificationEntrySchema, VerificationConfigSchema, CanonicalPathsSchema, VerificationTypesSchema, DetectionRuleEntrySchema, DetectionConfigSchema, WatchConfigSchema, AdapterLocalPathSchema, AdaptersConfigSchema, TelemetryConfigSchema, LSPConfigSchema, RawConfigSchema, _config, _resolvedApiKeySource, _projectRoot;
|
|
453
462
|
var init_config = __esm({
|
|
454
463
|
"src/config.ts"() {
|
|
455
464
|
"use strict";
|
|
456
465
|
init_memory_path();
|
|
466
|
+
init_corrections_md();
|
|
457
467
|
init_credentials();
|
|
458
468
|
init_config_memory_schema();
|
|
469
|
+
DEFAULT_KNOWLEDGE_SOURCE_FILES = [
|
|
470
|
+
"CLAUDE.md",
|
|
471
|
+
"MEMORY.md",
|
|
472
|
+
CORRECTIONS_FILENAME
|
|
473
|
+
];
|
|
459
474
|
DomainConfigSchema = z2.object({
|
|
460
475
|
name: z2.string().default("Unknown"),
|
|
461
476
|
routers: z2.array(z2.string()).default([]),
|
|
@@ -584,34 +599,40 @@ var init_config = __esm({
|
|
|
584
599
|
fixDetection: z2.object({
|
|
585
600
|
enabled: z2.boolean().default(true),
|
|
586
601
|
lookbackDays: z2.number().default(7),
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
602
|
+
// ⛔ NO DEFAULT LIST HERE, DELIBERATELY — the heuristics are the SSOT.
|
|
603
|
+
//
|
|
604
|
+
// This carried a hand-typed copy of all eight signal names while
|
|
605
|
+
// `fix-detector.ts` already read `config…signals ?? FIX_HEURISTICS.map(h => h.name)`.
|
|
606
|
+
// Because zod ALWAYS materialised the default, that fallback — the correct expression
|
|
607
|
+
// — was unreachable, and the duplicate was the only thing in effect. Two authoring
|
|
608
|
+
// sites for one vocabulary, and the drift is SILENT: a name listed here that no longer
|
|
609
|
+
// exists simply never matches `enabledSignals.has(...)`, so a retired heuristic lingers
|
|
610
|
+
// as a dead entry with no error. Retiring `method_name_correction` would have landed at
|
|
611
|
+
// one site of two (CR-74).
|
|
612
|
+
//
|
|
613
|
+
// Optional, so an ABSENT value reaches the `??` and the heuristic list defines itself
|
|
614
|
+
// (Rule 25). An explicit list in massu.config.yaml still overrides, which is the only
|
|
615
|
+
// behaviour anyone actually configured — measured: `fix-detector.ts:267` is the sole
|
|
616
|
+
// consumer and no config file in the repo sets it.
|
|
617
|
+
signals: z2.array(z2.string()).optional()
|
|
618
|
+
}).prefault({}),
|
|
598
619
|
failureClassification: z2.object({
|
|
599
620
|
enabled: z2.boolean().default(true),
|
|
600
621
|
thresholds: z2.object({
|
|
601
622
|
known: z2.number().default(5),
|
|
602
623
|
similar: z2.number().default(3)
|
|
603
|
-
}).
|
|
624
|
+
}).prefault({}),
|
|
604
625
|
scoring: z2.object({
|
|
605
626
|
diffPatternWeight: z2.number().default(3),
|
|
606
627
|
filePatternWeight: z2.number().default(2),
|
|
607
628
|
promptKeywordWeight: z2.number().default(2)
|
|
608
|
-
}).
|
|
609
|
-
}).
|
|
629
|
+
}).prefault({})
|
|
630
|
+
}).prefault({}),
|
|
610
631
|
pipeline: z2.object({
|
|
611
632
|
requireIncidentReport: z2.boolean().default(true),
|
|
612
633
|
requirePreventionRule: z2.boolean().default(true),
|
|
613
634
|
requireEnforcement: z2.boolean().default(true)
|
|
614
|
-
}).
|
|
635
|
+
}).prefault({}),
|
|
615
636
|
// plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
|
|
616
637
|
// custom destinations for the rule-candidate funnel. The classifier matches
|
|
617
638
|
// a candidate to one of these entries when none of the framework
|
|
@@ -644,7 +665,7 @@ var init_config = __esm({
|
|
|
644
665
|
memory: z2.boolean().default(true),
|
|
645
666
|
analytics: z2.boolean().default(true),
|
|
646
667
|
audit: z2.boolean().default(true)
|
|
647
|
-
}).
|
|
668
|
+
}).prefault({ memory: true, analytics: true, audit: true })
|
|
648
669
|
}).optional();
|
|
649
670
|
ConventionsConfigSchema = z2.object({
|
|
650
671
|
claudeDirName: z2.string().default(".claude").refine(
|
|
@@ -675,7 +696,7 @@ var init_config = __esm({
|
|
|
675
696
|
"session-state",
|
|
676
697
|
"agents"
|
|
677
698
|
]),
|
|
678
|
-
knowledgeSourceFiles: z2.array(z2.string()).default([
|
|
699
|
+
knowledgeSourceFiles: z2.array(z2.string()).default([...DEFAULT_KNOWLEDGE_SOURCE_FILES]),
|
|
679
700
|
excludePatterns: z2.array(z2.string()).default(["/ARCHIVE/", "/SESSION-HISTORY/"])
|
|
680
701
|
}).optional();
|
|
681
702
|
PythonDomainConfigSchema = z2.object({
|
|
@@ -691,6 +712,23 @@ var init_config = __esm({
|
|
|
691
712
|
}).optional();
|
|
692
713
|
PathsConfigSchema = z2.object({
|
|
693
714
|
source: z2.string().default("src"),
|
|
715
|
+
/**
|
|
716
|
+
* Where this stack's tests live, relative to the project root.
|
|
717
|
+
*
|
|
718
|
+
* Added 2026-09-20 for P4.3 of docs/plans/2026-09-20-stack-conformant-asset-rendering.md.
|
|
719
|
+
* ~25 shipped assets told every customer their tests belong in
|
|
720
|
+
* `packages/core/src/__tests__/` — massu's OWN layout — and no vocabulary could express
|
|
721
|
+
* the alternative because none existed. A test directory is a first-class property of
|
|
722
|
+
* every stack (`tests/` on FastAPI, `test/` on Rails, `src/test/java` on Spring,
|
|
723
|
+
* `Tests/` on SwiftPM, and Go keeps `_test.go` beside the source), so it belongs here
|
|
724
|
+
* rather than in prose.
|
|
725
|
+
*
|
|
726
|
+
* OPTIONAL, so every config written before this key existed keeps loading unchanged; an
|
|
727
|
+
* asset that needs it uses `{{paths.tests | default("...")}}`. All 13 shipped templates
|
|
728
|
+
* declare it, each with the convention it follows recorded at the site (G32 option 2),
|
|
729
|
+
* and `stack-template-conformance.test.ts` asserts that.
|
|
730
|
+
*/
|
|
731
|
+
tests: z2.string().optional(),
|
|
694
732
|
aliases: z2.record(z2.string(), z2.string()).default({ "@": "src" }),
|
|
695
733
|
monorepo_roots: z2.array(z2.string()).optional(),
|
|
696
734
|
routers: z2.string().optional(),
|
|
@@ -800,14 +838,14 @@ var init_config = __esm({
|
|
|
800
838
|
project: z2.object({
|
|
801
839
|
name: z2.string().default("my-project"),
|
|
802
840
|
root: z2.string().default("auto")
|
|
803
|
-
}).
|
|
804
|
-
framework: FrameworkConfigSchema.
|
|
841
|
+
}).prefault({ name: "my-project", root: "auto" }),
|
|
842
|
+
framework: FrameworkConfigSchema.prefault({
|
|
805
843
|
type: "typescript",
|
|
806
844
|
router: "none",
|
|
807
845
|
orm: "none",
|
|
808
846
|
ui: "none"
|
|
809
847
|
}),
|
|
810
|
-
paths: PathsConfigSchema.
|
|
848
|
+
paths: PathsConfigSchema.prefault({ source: "src", aliases: { "@": "src" } }),
|
|
811
849
|
toolPrefix: z2.string().default("massu"),
|
|
812
850
|
dbAccessPattern: z2.string().optional(),
|
|
813
851
|
knownMismatches: z2.record(z2.string(), z2.record(z2.string(), z2.string())).optional(),
|
|
@@ -4012,6 +4050,342 @@ import { existsSync as existsSync9, appendFileSync as appendFileSync3, mkdirSync
|
|
|
4012
4050
|
import { tmpdir as tmpdir2 } from "os";
|
|
4013
4051
|
import { join as join6 } from "path";
|
|
4014
4052
|
|
|
4053
|
+
// src/hooks/lib/code-spans.ts
|
|
4054
|
+
import { extname } from "path";
|
|
4055
|
+
var DQ = { open: '"', close: '"', escapes: true, multiline: false };
|
|
4056
|
+
var SQ = { open: "'", close: "'", escapes: true, multiline: false };
|
|
4057
|
+
var C_COMMENTS = { lineComments: ["//"], blockComments: [["/*", "*/"]] };
|
|
4058
|
+
var JS = {
|
|
4059
|
+
id: "js",
|
|
4060
|
+
...C_COMMENTS,
|
|
4061
|
+
strings: [DQ, SQ, { open: "`", close: "`", escapes: true, multiline: true, interpolation: true }],
|
|
4062
|
+
regexLiterals: true
|
|
4063
|
+
};
|
|
4064
|
+
var C = { id: "c", ...C_COMMENTS, strings: [DQ, SQ] };
|
|
4065
|
+
var GO = {
|
|
4066
|
+
id: "go",
|
|
4067
|
+
...C_COMMENTS,
|
|
4068
|
+
strings: [DQ, SQ, { open: "`", close: "`", escapes: false, multiline: true }]
|
|
4069
|
+
};
|
|
4070
|
+
var JVM = {
|
|
4071
|
+
id: "jvm",
|
|
4072
|
+
...C_COMMENTS,
|
|
4073
|
+
strings: [{ open: '"""', close: '"""', escapes: true, multiline: true }, DQ, SQ]
|
|
4074
|
+
};
|
|
4075
|
+
var SWIFT = {
|
|
4076
|
+
id: "swift",
|
|
4077
|
+
...C_COMMENTS,
|
|
4078
|
+
strings: [{ open: '"""', close: '"""', escapes: true, multiline: true }, DQ]
|
|
4079
|
+
};
|
|
4080
|
+
var RUST = {
|
|
4081
|
+
id: "rust",
|
|
4082
|
+
...C_COMMENTS,
|
|
4083
|
+
strings: [{ open: '"', close: '"', escapes: true, multiline: true }],
|
|
4084
|
+
rustChars: true
|
|
4085
|
+
};
|
|
4086
|
+
var PYTHON = {
|
|
4087
|
+
id: "python",
|
|
4088
|
+
lineComments: ["#"],
|
|
4089
|
+
blockComments: [],
|
|
4090
|
+
strings: [
|
|
4091
|
+
{ open: '"""', close: '"""', escapes: true, multiline: true },
|
|
4092
|
+
{ open: "'''", close: "'''", escapes: true, multiline: true },
|
|
4093
|
+
DQ,
|
|
4094
|
+
SQ
|
|
4095
|
+
]
|
|
4096
|
+
};
|
|
4097
|
+
var SHELL = {
|
|
4098
|
+
id: "shell",
|
|
4099
|
+
lineComments: ["#"],
|
|
4100
|
+
blockComments: [],
|
|
4101
|
+
strings: [
|
|
4102
|
+
{ open: '"', close: '"', escapes: true, multiline: true },
|
|
4103
|
+
{ open: "'", close: "'", escapes: false, multiline: true }
|
|
4104
|
+
],
|
|
4105
|
+
hashNeedsBoundary: true
|
|
4106
|
+
};
|
|
4107
|
+
var RUBY = {
|
|
4108
|
+
id: "ruby",
|
|
4109
|
+
lineComments: ["#"],
|
|
4110
|
+
blockComments: [],
|
|
4111
|
+
strings: [
|
|
4112
|
+
{ open: '"', close: '"', escapes: true, multiline: true },
|
|
4113
|
+
{ open: "'", close: "'", escapes: true, multiline: true }
|
|
4114
|
+
]
|
|
4115
|
+
};
|
|
4116
|
+
var LEX_SPECS = Object.freeze({
|
|
4117
|
+
".ts": JS,
|
|
4118
|
+
".tsx": JS,
|
|
4119
|
+
".mts": JS,
|
|
4120
|
+
".cts": JS,
|
|
4121
|
+
".js": JS,
|
|
4122
|
+
".jsx": JS,
|
|
4123
|
+
".mjs": JS,
|
|
4124
|
+
".cjs": JS,
|
|
4125
|
+
".c": C,
|
|
4126
|
+
".h": C,
|
|
4127
|
+
".cc": C,
|
|
4128
|
+
".cpp": C,
|
|
4129
|
+
".hpp": C,
|
|
4130
|
+
".cs": C,
|
|
4131
|
+
".dart": C,
|
|
4132
|
+
".m": C,
|
|
4133
|
+
".mm": C,
|
|
4134
|
+
".go": GO,
|
|
4135
|
+
".java": JVM,
|
|
4136
|
+
".kt": JVM,
|
|
4137
|
+
".kts": JVM,
|
|
4138
|
+
".scala": JVM,
|
|
4139
|
+
".groovy": JVM,
|
|
4140
|
+
".swift": SWIFT,
|
|
4141
|
+
".rs": RUST,
|
|
4142
|
+
".py": PYTHON,
|
|
4143
|
+
".pyi": PYTHON,
|
|
4144
|
+
".sh": SHELL,
|
|
4145
|
+
".bash": SHELL,
|
|
4146
|
+
".zsh": SHELL,
|
|
4147
|
+
".rb": RUBY
|
|
4148
|
+
});
|
|
4149
|
+
function lexSpecFor(path) {
|
|
4150
|
+
const ext = extname(path).toLowerCase();
|
|
4151
|
+
return Object.prototype.hasOwnProperty.call(LEX_SPECS, ext) ? LEX_SPECS[ext] : null;
|
|
4152
|
+
}
|
|
4153
|
+
var RUST_CHAR_RE = /^'(?:\\(?:u\{[0-9a-fA-F]{1,6}\}|x[0-9a-fA-F]{2}|.)|[^\\'\n])'/;
|
|
4154
|
+
var IDENT_CHAR_RE = /[A-Za-z0-9_$]/;
|
|
4155
|
+
var SHELL_WORD_BREAK = /* @__PURE__ */ new Set([" ", " ", ";", "|", "&", "(", ")"]);
|
|
4156
|
+
var REGEX_AFTER_KEYWORD = /* @__PURE__ */ new Set([
|
|
4157
|
+
"return",
|
|
4158
|
+
"typeof",
|
|
4159
|
+
"case",
|
|
4160
|
+
"in",
|
|
4161
|
+
"of",
|
|
4162
|
+
"delete",
|
|
4163
|
+
"void",
|
|
4164
|
+
"throw",
|
|
4165
|
+
"new",
|
|
4166
|
+
"yield",
|
|
4167
|
+
"await",
|
|
4168
|
+
"else",
|
|
4169
|
+
"do"
|
|
4170
|
+
]);
|
|
4171
|
+
function regexLiteralLength(line, i, lastSig) {
|
|
4172
|
+
if (lastSig === ")" || lastSig === "]") return 0;
|
|
4173
|
+
if (lastSig !== "" && IDENT_CHAR_RE.test(lastSig)) {
|
|
4174
|
+
const m = /([A-Za-z_$][A-Za-z0-9_$]*)\s*$/.exec(line.slice(0, i));
|
|
4175
|
+
if (!m || !REGEX_AFTER_KEYWORD.has(m[1])) return 0;
|
|
4176
|
+
}
|
|
4177
|
+
let inClass = false;
|
|
4178
|
+
for (let j = i + 1; j < line.length; j++) {
|
|
4179
|
+
const ch = line[j];
|
|
4180
|
+
if (ch === "\\") {
|
|
4181
|
+
j++;
|
|
4182
|
+
continue;
|
|
4183
|
+
}
|
|
4184
|
+
if (inClass) {
|
|
4185
|
+
if (ch === "]") inClass = false;
|
|
4186
|
+
continue;
|
|
4187
|
+
}
|
|
4188
|
+
if (ch === "[") {
|
|
4189
|
+
inClass = true;
|
|
4190
|
+
continue;
|
|
4191
|
+
}
|
|
4192
|
+
if (ch === "/") return j - i + 1;
|
|
4193
|
+
}
|
|
4194
|
+
return 0;
|
|
4195
|
+
}
|
|
4196
|
+
function projectLine(line, spec, st) {
|
|
4197
|
+
const out = line.split("");
|
|
4198
|
+
const blank = (from, len) => {
|
|
4199
|
+
for (let k = from; k < from + len && k < out.length; k++) out[k] = " ";
|
|
4200
|
+
};
|
|
4201
|
+
let i = 0;
|
|
4202
|
+
while (i < line.length) {
|
|
4203
|
+
const mode = st.mode;
|
|
4204
|
+
if (mode.kind === "block") {
|
|
4205
|
+
if (line.startsWith(mode.close, i)) {
|
|
4206
|
+
blank(i, mode.close.length);
|
|
4207
|
+
i += mode.close.length;
|
|
4208
|
+
st.mode = { kind: "code" };
|
|
4209
|
+
} else {
|
|
4210
|
+
blank(i, 1);
|
|
4211
|
+
i++;
|
|
4212
|
+
}
|
|
4213
|
+
continue;
|
|
4214
|
+
}
|
|
4215
|
+
if (mode.kind === "string") {
|
|
4216
|
+
const r = mode.rule;
|
|
4217
|
+
if (r.escapes && line[i] === "\\") {
|
|
4218
|
+
blank(i, 2);
|
|
4219
|
+
i += 2;
|
|
4220
|
+
continue;
|
|
4221
|
+
}
|
|
4222
|
+
if (r.interpolation && line.startsWith("${", i)) {
|
|
4223
|
+
blank(i, 2);
|
|
4224
|
+
i += 2;
|
|
4225
|
+
st.holes.push(0);
|
|
4226
|
+
st.mode = { kind: "code" };
|
|
4227
|
+
st.lastSig = "";
|
|
4228
|
+
continue;
|
|
4229
|
+
}
|
|
4230
|
+
if (line.startsWith(r.close, i)) {
|
|
4231
|
+
blank(i, r.close.length);
|
|
4232
|
+
i += r.close.length;
|
|
4233
|
+
st.mode = { kind: "code" };
|
|
4234
|
+
st.lastSig = '"';
|
|
4235
|
+
continue;
|
|
4236
|
+
}
|
|
4237
|
+
blank(i, 1);
|
|
4238
|
+
i++;
|
|
4239
|
+
continue;
|
|
4240
|
+
}
|
|
4241
|
+
const ch = line[i];
|
|
4242
|
+
if (st.holes.length > 0) {
|
|
4243
|
+
const top = st.holes.length - 1;
|
|
4244
|
+
if (ch === "{") st.holes[top]++;
|
|
4245
|
+
else if (ch === "}") {
|
|
4246
|
+
if (st.holes[top] === 0) {
|
|
4247
|
+
st.holes.pop();
|
|
4248
|
+
blank(i, 1);
|
|
4249
|
+
i++;
|
|
4250
|
+
st.mode = { kind: "string", rule: spec.strings.find((s) => s.interpolation) };
|
|
4251
|
+
continue;
|
|
4252
|
+
}
|
|
4253
|
+
st.holes[top]--;
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
const lc = spec.lineComments.find((m) => line.startsWith(m, i));
|
|
4257
|
+
if (lc && (!spec.hashNeedsBoundary || i === 0 || SHELL_WORD_BREAK.has(line[i - 1]))) {
|
|
4258
|
+
blank(i, line.length - i);
|
|
4259
|
+
break;
|
|
4260
|
+
}
|
|
4261
|
+
const bc = spec.blockComments.find(([open]) => line.startsWith(open, i));
|
|
4262
|
+
if (bc) {
|
|
4263
|
+
blank(i, bc[0].length);
|
|
4264
|
+
i += bc[0].length;
|
|
4265
|
+
st.mode = { kind: "block", close: bc[1] };
|
|
4266
|
+
continue;
|
|
4267
|
+
}
|
|
4268
|
+
if (spec.rustChars && ch === "'") {
|
|
4269
|
+
const m = RUST_CHAR_RE.exec(line.slice(i));
|
|
4270
|
+
if (m) {
|
|
4271
|
+
blank(i, m[0].length);
|
|
4272
|
+
i += m[0].length;
|
|
4273
|
+
st.lastSig = "'";
|
|
4274
|
+
continue;
|
|
4275
|
+
}
|
|
4276
|
+
i++;
|
|
4277
|
+
continue;
|
|
4278
|
+
}
|
|
4279
|
+
const sr = spec.strings.find((s) => line.startsWith(s.open, i));
|
|
4280
|
+
if (sr) {
|
|
4281
|
+
blank(i, sr.open.length);
|
|
4282
|
+
i += sr.open.length;
|
|
4283
|
+
st.mode = { kind: "string", rule: sr };
|
|
4284
|
+
continue;
|
|
4285
|
+
}
|
|
4286
|
+
if (spec.regexLiterals && ch === "/") {
|
|
4287
|
+
const len = regexLiteralLength(line, i, st.lastSig);
|
|
4288
|
+
if (len > 0) {
|
|
4289
|
+
blank(i, len);
|
|
4290
|
+
i += len;
|
|
4291
|
+
st.lastSig = "/";
|
|
4292
|
+
continue;
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
if (ch !== " " && ch !== " ") st.lastSig = ch;
|
|
4296
|
+
i++;
|
|
4297
|
+
}
|
|
4298
|
+
if (st.mode.kind === "string" && !st.mode.rule.multiline) st.mode = { kind: "code" };
|
|
4299
|
+
return out.join("");
|
|
4300
|
+
}
|
|
4301
|
+
function projectCode(lines, spec) {
|
|
4302
|
+
const st = { mode: { kind: "code" }, holes: [], lastSig: "" };
|
|
4303
|
+
return lines.map((l) => projectLine(l, spec, st));
|
|
4304
|
+
}
|
|
4305
|
+
function unquoteGitPath(p) {
|
|
4306
|
+
const s = p.startsWith('"') && p.endsWith('"') ? p.slice(1, -1) : p;
|
|
4307
|
+
return s.startsWith("b/") ? s.slice(2) : s;
|
|
4308
|
+
}
|
|
4309
|
+
function diffCodeView(diff, opts = {}) {
|
|
4310
|
+
const files = [];
|
|
4311
|
+
let cur = null;
|
|
4312
|
+
let hunk = null;
|
|
4313
|
+
let newLine = 0;
|
|
4314
|
+
let inHeader = false;
|
|
4315
|
+
const startFile = (path) => {
|
|
4316
|
+
const f = { path, hunks: [], removed: [] };
|
|
4317
|
+
files.push(f);
|
|
4318
|
+
return f;
|
|
4319
|
+
};
|
|
4320
|
+
for (const line of diff.split("\n")) {
|
|
4321
|
+
if (line.startsWith("diff --git ")) {
|
|
4322
|
+
cur = startFile("");
|
|
4323
|
+
hunk = null;
|
|
4324
|
+
inHeader = true;
|
|
4325
|
+
continue;
|
|
4326
|
+
}
|
|
4327
|
+
if (inHeader && line.startsWith("+++ ")) {
|
|
4328
|
+
const p = line.slice(4).trim();
|
|
4329
|
+
if (cur) cur.path = p === "/dev/null" ? "" : unquoteGitPath(p);
|
|
4330
|
+
continue;
|
|
4331
|
+
}
|
|
4332
|
+
if (inHeader && (line.startsWith("--- ") || !line.startsWith("@@"))) continue;
|
|
4333
|
+
const hh = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
|
|
4334
|
+
if (hh) {
|
|
4335
|
+
inHeader = false;
|
|
4336
|
+
if (!cur) cur = startFile("");
|
|
4337
|
+
hunk = { newSide: [], addedAt: [] };
|
|
4338
|
+
cur.hunks.push(hunk);
|
|
4339
|
+
newLine = parseInt(hh[1], 10);
|
|
4340
|
+
continue;
|
|
4341
|
+
}
|
|
4342
|
+
if (!cur) cur = startFile("");
|
|
4343
|
+
if (!hunk) {
|
|
4344
|
+
hunk = { newSide: [], addedAt: [] };
|
|
4345
|
+
cur.hunks.push(hunk);
|
|
4346
|
+
newLine = 1;
|
|
4347
|
+
}
|
|
4348
|
+
if (line.startsWith("+")) {
|
|
4349
|
+
hunk.addedAt.push({ idx: hunk.newSide.length, lineNo: newLine });
|
|
4350
|
+
hunk.newSide.push(line.slice(1));
|
|
4351
|
+
newLine++;
|
|
4352
|
+
} else if (line.startsWith("-")) {
|
|
4353
|
+
cur.removed.push(line.slice(1));
|
|
4354
|
+
} else if (line.startsWith(" ")) {
|
|
4355
|
+
hunk.newSide.push(line.slice(1));
|
|
4356
|
+
newLine++;
|
|
4357
|
+
}
|
|
4358
|
+
}
|
|
4359
|
+
return files.map((f) => {
|
|
4360
|
+
const spec = f.path ? lexSpecFor(f.path) : null;
|
|
4361
|
+
const rawAdded = f.hunks.flatMap(
|
|
4362
|
+
(h) => h.addedAt.map((a) => ({ lineNo: a.lineNo, raw: h.newSide[a.idx] }))
|
|
4363
|
+
);
|
|
4364
|
+
if (!spec) {
|
|
4365
|
+
return { path: f.path, mode: "raw", added: rawAdded.map((a) => ({ ...a, code: a.raw })), removed: f.removed };
|
|
4366
|
+
}
|
|
4367
|
+
const text = opts.postImage ? opts.postImage(f.path) : null;
|
|
4368
|
+
if (text !== null && text !== void 0) {
|
|
4369
|
+
const post = text.split(/\r\n|\n|\r/);
|
|
4370
|
+
if (rawAdded.every((a) => post[a.lineNo - 1] === a.raw)) {
|
|
4371
|
+
const proj = projectCode(post, spec);
|
|
4372
|
+
return {
|
|
4373
|
+
path: f.path,
|
|
4374
|
+
mode: "post-image",
|
|
4375
|
+
added: rawAdded.map((a) => ({ ...a, code: proj[a.lineNo - 1] })),
|
|
4376
|
+
removed: f.removed
|
|
4377
|
+
};
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
const added = [];
|
|
4381
|
+
for (const h of f.hunks) {
|
|
4382
|
+
const proj = projectCode(h.newSide, spec);
|
|
4383
|
+
for (const a of h.addedAt) added.push({ lineNo: a.lineNo, raw: h.newSide[a.idx], code: proj[a.idx] });
|
|
4384
|
+
}
|
|
4385
|
+
return { path: f.path, mode: "hunk", added, removed: f.removed };
|
|
4386
|
+
});
|
|
4387
|
+
}
|
|
4388
|
+
|
|
4015
4389
|
// src/hooks/lib/write-hook-message.ts
|
|
4016
4390
|
var HOOK_EVENTS = [
|
|
4017
4391
|
"UserPromptSubmit",
|
|
@@ -4079,22 +4453,70 @@ var FIX_HEURISTICS = [
|
|
|
4079
4453
|
},
|
|
4080
4454
|
{
|
|
4081
4455
|
name: "added_error_handling",
|
|
4456
|
+
// `if.*nil` / `if.*None` were DROPPED here, and that is deduplication rather than a silent
|
|
4457
|
+
// narrowing: `nil_handling_fix` below already matches exactly that shape
|
|
4458
|
+
// (`= nil|= None|is None|!= nil|is not None`), so a nil check scored twice under one banner.
|
|
4459
|
+
// Scored on each added line's CODE projection (`lib/code-spans.ts`), the same lexer the
|
|
4460
|
+
// Stop hook uses: a keyword in a string literal or a comment is not error handling. The
|
|
4461
|
+
// per-edit diff carries its `diff --git` header, so the file's language is known here
|
|
4462
|
+
// without threading a path through this table. A language with no lexer is judged raw.
|
|
4082
4463
|
test: (diff) => {
|
|
4083
|
-
const
|
|
4464
|
+
const re = /^\+.*\b(try|except|catch|guard|validate|assert|raise|throw)\b/;
|
|
4465
|
+
let added = 0;
|
|
4466
|
+
for (const view of diffCodeView(diff)) {
|
|
4467
|
+
for (const a of view.added) if (re.test(`+${a.code}`)) added++;
|
|
4468
|
+
}
|
|
4084
4469
|
return added > 2;
|
|
4085
4470
|
}
|
|
4086
4471
|
},
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4472
|
+
// ⛔ `method_name_correction` WAS RETIRED HERE 2026-08-20, and the measurement is why.
|
|
4473
|
+
//
|
|
4474
|
+
// test: (diff) => {
|
|
4475
|
+
// const removed = diff.match(/^-.*\.([a-z_]+)\(/m); // FIRST removed method call
|
|
4476
|
+
// const added = diff.match(/^\+.*\.([a-z_]+)\(/m); // FIRST added method call
|
|
4477
|
+
// return !!(removed && added && removed[1] !== added[1]);
|
|
4478
|
+
// }
|
|
4479
|
+
//
|
|
4480
|
+
// It compared the FIRST removed call against the FIRST added call, anywhere in the diff.
|
|
4481
|
+
// The two need no relationship to each other, so it fired whenever a diff contained any
|
|
4482
|
+
// two differently-named method calls — which is nearly every diff that touches code.
|
|
4483
|
+
//
|
|
4484
|
+
// Measured over the last 250 commits repo-wide:
|
|
4485
|
+
// shipped predicate 29 firings (12%)
|
|
4486
|
+
// one requiring the two lines to be identical but for the name 0 firings
|
|
4487
|
+
// Eleven firings read by hand over packages/core/src: `.push()`->`.includes()`,
|
|
4488
|
+
// `.call()`->`.system()`, `.trim()`->`.stringify()`, `.all()`->`.cwd()`, `.now()`->`.push()`,
|
|
4489
|
+
// `.on()`->`.sort()`, `.test()`->`.split()` … NOT ONE was a rename. Precision 0 of 29.
|
|
4490
|
+
//
|
|
4491
|
+
// RETIRED RATHER THAN REPAIRED, on the same numbers: a correct implementation fires ZERO
|
|
4492
|
+
// times in 250 commits, so repairing it buys a branch that never runs — dead weight with a
|
|
4493
|
+
// maintenance cost (CR-71 in spirit: a detector that cannot fire is not a detector). It was
|
|
4494
|
+
// not free while it lived: signals feed `sessionFixes`, which drives the ACTION REQUIRED
|
|
4495
|
+
// banner demanding incidents, so this put noise directly on the retro pipeline — the one
|
|
4496
|
+
// banner that must not cry wolf (CR-83).
|
|
4497
|
+
//
|
|
4498
|
+
// Kept as a comment, not deleted outright, so the next author who reaches for
|
|
4499
|
+
// "detect a method rename from a diff" finds the measurement instead of rebuilding it.
|
|
4500
|
+
//
|
|
4501
|
+
// ⛔ THE FULL WRITE-UP IS DELIBERATELY NOT LINKED FROM HERE. This file SYNCS TO THE PUBLIC
|
|
4502
|
+
// MIRROR and the incident corpus does not, so a path citation would point at something
|
|
4503
|
+
// that does not exist there — and the leak guard refuses it, correctly. It caught exactly
|
|
4504
|
+
// this line. The measurement above is self-contained for that reason; search the internal
|
|
4505
|
+
// incident corpus for "a summary counted signals and listed files" if you need the rest.
|
|
4506
|
+
// (Memory: a-retro-citation-is-publishable-content.)
|
|
4095
4507
|
{
|
|
4096
4508
|
name: "auth_fix",
|
|
4097
|
-
|
|
4509
|
+
// `authn`/`authz` are spelled out because `\bauth\b` no longer reaches them, and the
|
|
4510
|
+
// trailing `s?` keeps plurals (`tokens`, `credential`+`s`) that the boundary would drop.
|
|
4511
|
+
//
|
|
4512
|
+
// ⚠️ Backticks, not double quotes, and the plural is split — deliberately. CR-59's
|
|
4513
|
+
// drift-guard forbids a quoted `credentials` segment literal outside credentials.ts via
|
|
4514
|
+
// /['"]credentials['"]/, which cannot tell a string literal in CODE from quotation marks
|
|
4515
|
+
// in PROSE. Writing the word normally here turns that guard red. That is the SAME defect
|
|
4516
|
+
// class this file's own boundary fix addresses, in a second detector — recorded in the
|
|
4517
|
+
// incident rather than fixed here, because widening a security-adjacent guard is its own
|
|
4518
|
+
// change with its own blast radius.
|
|
4519
|
+
test: (diff) => /^\+.*\b(token|auth|authn|authz|header|X-Service|Bearer|credential)s?\b/im.test(diff)
|
|
4098
4520
|
},
|
|
4099
4521
|
{
|
|
4100
4522
|
name: "nil_handling_fix",
|
|
@@ -4102,16 +4524,42 @@ var FIX_HEURISTICS = [
|
|
|
4102
4524
|
},
|
|
4103
4525
|
{
|
|
4104
4526
|
name: "concurrency_fix",
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4527
|
+
// `rate[-_. ]limit` — the original `rate.limit` used an UNESCAPED dot, so `.` matched any
|
|
4528
|
+
// character and the alternative fired on "corporate_limits". The class is now explicit
|
|
4529
|
+
// about which separators are intended.
|
|
4530
|
+
test: (diff) => /^\+.*\b(timeout|semaphore|lock|mutex|throttle|rate[-_. ]limit|max_conn)\b/im.test(diff)
|
|
4531
|
+
}
|
|
4532
|
+
// ⛔ `async_pattern_fix` AND `added_missing_import` WERE RETIRED HERE 2026-08-20.
|
|
4533
|
+
//
|
|
4534
|
+
// async_pattern_fix:
|
|
4535
|
+
// test: (diff) => /^\+.*(@MainActor|async with|asyncio\.timeout|\.await)/.test(diff)
|
|
4536
|
+
// && /^-/m.test(diff),
|
|
4537
|
+
// added_missing_import:
|
|
4538
|
+
// test: (diff) => /^\+.*(import|from.*import|require)/.test(diff)
|
|
4539
|
+
// && !/^-.*(import|from.*import|require)/m.test(diff),
|
|
4540
|
+
//
|
|
4541
|
+
// Both fire ZERO times across the last 250 commits — measured on the same run that produced
|
|
4542
|
+
// the boundary numbers above, over the 87 commits touching non-test source:
|
|
4543
|
+
//
|
|
4544
|
+
// async_pattern_fix 0 / 87
|
|
4545
|
+
// added_missing_import 0 / 87
|
|
4546
|
+
//
|
|
4547
|
+
// This is the identical finding that retired `method_name_correction` below, and it is
|
|
4548
|
+
// retired for the identical reason: a detector that never fires is not a detector, and it
|
|
4549
|
+
// still costs review attention and maintenance on every pass over this table (CR-71 in
|
|
4550
|
+
// spirit — a zero-caller chokepoint is a dead feature).
|
|
4551
|
+
//
|
|
4552
|
+
// WHY THEY CANNOT FIRE HERE, so the next author does not "fix" them back in: their tokens are
|
|
4553
|
+
// largely foreign to this codebase. `@MainActor` is Swift; `async with` is Python. Neither
|
|
4554
|
+
// appears in a TypeScript monorepo. `added_missing_import` requires added imports with NO
|
|
4555
|
+
// removed import anywhere in the same diff, which any edit touching an import block violates.
|
|
4556
|
+
// If this table is ever pointed at a Swift or Python corpus, re-measure before reinstating —
|
|
4557
|
+
// do not reinstate on the assumption that the language mix changed.
|
|
4558
|
+
//
|
|
4559
|
+
// Kept as comments rather than deleted so the measurement travels with the code, and so a
|
|
4560
|
+
// future author reaching for "detect an async-pattern fix" finds the number first.
|
|
4561
|
+
// (Same reasoning, and same publication constraint, as the block below: this file syncs to
|
|
4562
|
+
// the public mirror, so no internal path citations.)
|
|
4115
4563
|
];
|
|
4116
4564
|
function getSessionFlagPath(sessionId) {
|
|
4117
4565
|
const dir = join6(tmpdir2(), "massu-auto-learning");
|