@massu/core 2.5.1 → 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 +240 -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 +1731 -976
- package/dist/hooks/auto-learning-pipeline.js +745 -87
- package/dist/hooks/classify-failure.js +747 -45
- package/dist/hooks/cost-tracker.js +89 -28
- package/dist/hooks/fix-detector.js +536 -48
- package/dist/hooks/incident-pipeline.js +96 -29
- package/dist/hooks/intent-suggester.js +89 -28
- package/dist/hooks/memory-recall.js +89 -28
- package/dist/hooks/post-edit-context.js +137 -29
- package/dist/hooks/post-tool-use.js +95 -30
- package/dist/hooks/pre-compact.js +89 -28
- package/dist/hooks/pre-delete-check.js +137 -31
- package/dist/hooks/pre-tool-use-gate.js +271 -43
- package/dist/hooks/quality-event.js +89 -28
- package/dist/hooks/rule-enforcement-pipeline.js +89 -28
- package/dist/hooks/security-gate.js +219 -39
- package/dist/hooks/session-end.js +92 -28
- package/dist/hooks/session-start.js +140 -62
- package/dist/hooks/user-prompt.js +89 -28
- 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
|
@@ -597,6 +597,40 @@ var init_memory_path = __esm({
|
|
|
597
597
|
}
|
|
598
598
|
});
|
|
599
599
|
|
|
600
|
+
// src/lib/corrections-md.ts
|
|
601
|
+
function parseCorrectionRules(content) {
|
|
602
|
+
const rules = [];
|
|
603
|
+
for (const raw of content.split("\n")) {
|
|
604
|
+
const line = raw.trim();
|
|
605
|
+
const m = line.match(CORRECTION_RULE_BULLET);
|
|
606
|
+
if (m && m[1].trim()) {
|
|
607
|
+
rules.push(m[1].trim());
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (line.startsWith("|") && line.endsWith("|")) {
|
|
611
|
+
const cells = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
612
|
+
if (cells.length < 4) continue;
|
|
613
|
+
if (cells[0] === "Date" || cells[0].startsWith("-")) continue;
|
|
614
|
+
const prevention = cells[3];
|
|
615
|
+
if (prevention && !prevention.startsWith("-") && !prevention.startsWith("<!--")) {
|
|
616
|
+
rules.push(prevention);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return rules;
|
|
621
|
+
}
|
|
622
|
+
function correctionsPath(memoryDir) {
|
|
623
|
+
return memoryDir.endsWith("/") ? `${memoryDir}${CORRECTIONS_FILENAME}` : `${memoryDir}/${CORRECTIONS_FILENAME}`;
|
|
624
|
+
}
|
|
625
|
+
var CORRECTION_RULE_BULLET, CORRECTIONS_FILENAME;
|
|
626
|
+
var init_corrections_md = __esm({
|
|
627
|
+
"src/lib/corrections-md.ts"() {
|
|
628
|
+
"use strict";
|
|
629
|
+
CORRECTION_RULE_BULLET = /^-\s+\*\*Rule\*\*:\s*(.+)$/;
|
|
630
|
+
CORRECTIONS_FILENAME = "corrections.md";
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
600
634
|
// src/config-memory-schema.ts
|
|
601
635
|
import { z } from "zod";
|
|
602
636
|
var MemoryConfigSchema;
|
|
@@ -630,7 +664,7 @@ var init_config_memory_schema = __esm({
|
|
|
630
664
|
message: "memory.recall.embedEndpoint must be an http(s) URL"
|
|
631
665
|
}).optional(),
|
|
632
666
|
embedModel: z.string().min(1).optional()
|
|
633
|
-
}).
|
|
667
|
+
}).prefault({}),
|
|
634
668
|
// --- Contradiction / supersede gate (plan-living-memory-slice-2-temporal-model, P5-001) ---
|
|
635
669
|
// When a new high-value memory (decision/correction) is written, find
|
|
636
670
|
// semantically-related existing records and, if the new one contradicts an
|
|
@@ -659,7 +693,7 @@ var init_config_memory_schema = __esm({
|
|
|
659
693
|
annotateSuperseded: z.boolean().default(false),
|
|
660
694
|
// Time budget (ms) for the contradiction check; exceeded → fail-open (ADD).
|
|
661
695
|
budgetMs: z.number().int().positive().default(800)
|
|
662
|
-
}).
|
|
696
|
+
}).prefault({}),
|
|
663
697
|
// --- Background consolidation, the "sleep-time" pass
|
|
664
698
|
// (plan-living-memory-slice-3-consolidation) ---
|
|
665
699
|
// Keeps memory sharp over years: dedupes, distills dying sessions into
|
|
@@ -705,7 +739,7 @@ var init_config_memory_schema = __esm({
|
|
|
705
739
|
// Surface optional upgrades (e.g. "a local model was detected") in chat.
|
|
706
740
|
suggestUpgrades: z.boolean().default(true),
|
|
707
741
|
suggestIntervalDays: z.number().int().positive().default(30)
|
|
708
|
-
}).
|
|
742
|
+
}).prefault({}),
|
|
709
743
|
// A-20 (Slice 4) — memory FILES: the mirror, and the one switch that decides whether
|
|
710
744
|
// Massu may ever WRITE into the user's memory directory.
|
|
711
745
|
//
|
|
@@ -730,7 +764,7 @@ var init_config_memory_schema = __esm({
|
|
|
730
764
|
// turn of EVERY session, so an unbounded index is a permanent context tax — and the
|
|
731
765
|
// per-session cap bounds only the RATE, never the total.
|
|
732
766
|
indexMaxLines: z.number().int().min(1).default(50)
|
|
733
|
-
}).
|
|
767
|
+
}).prefault({}),
|
|
734
768
|
// --- Cross-repo memory surfacing (plan-living-memory-slice-5-cross-repo-surfacing) ---
|
|
735
769
|
// A decision made in one of your repos can surface in another — opt-in per
|
|
736
770
|
// decision AND opt-in per repo, signed, verified, and materialized ONLY on
|
|
@@ -755,8 +789,8 @@ var init_config_memory_schema = __esm({
|
|
|
755
789
|
enabled: z.boolean().default(true),
|
|
756
790
|
maxCrossRepoItems: z.number().int().min(0).default(1),
|
|
757
791
|
minScore: z.number().optional()
|
|
758
|
-
}).
|
|
759
|
-
}).
|
|
792
|
+
}).prefault({})
|
|
793
|
+
}).prefault({})
|
|
760
794
|
}).optional();
|
|
761
795
|
}
|
|
762
796
|
});
|
|
@@ -936,13 +970,19 @@ function getResolvedPaths() {
|
|
|
936
970
|
settingsLocalPath: resolve2(root, claudeDirName, "settings.local.json")
|
|
937
971
|
};
|
|
938
972
|
}
|
|
939
|
-
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;
|
|
973
|
+
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;
|
|
940
974
|
var init_config = __esm({
|
|
941
975
|
"src/config.ts"() {
|
|
942
976
|
"use strict";
|
|
943
977
|
init_memory_path();
|
|
978
|
+
init_corrections_md();
|
|
944
979
|
init_credentials();
|
|
945
980
|
init_config_memory_schema();
|
|
981
|
+
DEFAULT_KNOWLEDGE_SOURCE_FILES = [
|
|
982
|
+
"CLAUDE.md",
|
|
983
|
+
"MEMORY.md",
|
|
984
|
+
CORRECTIONS_FILENAME
|
|
985
|
+
];
|
|
946
986
|
DomainConfigSchema = z2.object({
|
|
947
987
|
name: z2.string().default("Unknown"),
|
|
948
988
|
routers: z2.array(z2.string()).default([]),
|
|
@@ -1071,34 +1111,40 @@ var init_config = __esm({
|
|
|
1071
1111
|
fixDetection: z2.object({
|
|
1072
1112
|
enabled: z2.boolean().default(true),
|
|
1073
1113
|
lookbackDays: z2.number().default(7),
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1114
|
+
// ⛔ NO DEFAULT LIST HERE, DELIBERATELY — the heuristics are the SSOT.
|
|
1115
|
+
//
|
|
1116
|
+
// This carried a hand-typed copy of all eight signal names while
|
|
1117
|
+
// `fix-detector.ts` already read `config…signals ?? FIX_HEURISTICS.map(h => h.name)`.
|
|
1118
|
+
// Because zod ALWAYS materialised the default, that fallback — the correct expression
|
|
1119
|
+
// — was unreachable, and the duplicate was the only thing in effect. Two authoring
|
|
1120
|
+
// sites for one vocabulary, and the drift is SILENT: a name listed here that no longer
|
|
1121
|
+
// exists simply never matches `enabledSignals.has(...)`, so a retired heuristic lingers
|
|
1122
|
+
// as a dead entry with no error. Retiring `method_name_correction` would have landed at
|
|
1123
|
+
// one site of two (CR-74).
|
|
1124
|
+
//
|
|
1125
|
+
// Optional, so an ABSENT value reaches the `??` and the heuristic list defines itself
|
|
1126
|
+
// (Rule 25). An explicit list in massu.config.yaml still overrides, which is the only
|
|
1127
|
+
// behaviour anyone actually configured — measured: `fix-detector.ts:267` is the sole
|
|
1128
|
+
// consumer and no config file in the repo sets it.
|
|
1129
|
+
signals: z2.array(z2.string()).optional()
|
|
1130
|
+
}).prefault({}),
|
|
1085
1131
|
failureClassification: z2.object({
|
|
1086
1132
|
enabled: z2.boolean().default(true),
|
|
1087
1133
|
thresholds: z2.object({
|
|
1088
1134
|
known: z2.number().default(5),
|
|
1089
1135
|
similar: z2.number().default(3)
|
|
1090
|
-
}).
|
|
1136
|
+
}).prefault({}),
|
|
1091
1137
|
scoring: z2.object({
|
|
1092
1138
|
diffPatternWeight: z2.number().default(3),
|
|
1093
1139
|
filePatternWeight: z2.number().default(2),
|
|
1094
1140
|
promptKeywordWeight: z2.number().default(2)
|
|
1095
|
-
}).
|
|
1096
|
-
}).
|
|
1141
|
+
}).prefault({})
|
|
1142
|
+
}).prefault({}),
|
|
1097
1143
|
pipeline: z2.object({
|
|
1098
1144
|
requireIncidentReport: z2.boolean().default(true),
|
|
1099
1145
|
requirePreventionRule: z2.boolean().default(true),
|
|
1100
1146
|
requireEnforcement: z2.boolean().default(true)
|
|
1101
|
-
}).
|
|
1147
|
+
}).prefault({}),
|
|
1102
1148
|
// plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
|
|
1103
1149
|
// custom destinations for the rule-candidate funnel. The classifier matches
|
|
1104
1150
|
// a candidate to one of these entries when none of the framework
|
|
@@ -1131,7 +1177,7 @@ var init_config = __esm({
|
|
|
1131
1177
|
memory: z2.boolean().default(true),
|
|
1132
1178
|
analytics: z2.boolean().default(true),
|
|
1133
1179
|
audit: z2.boolean().default(true)
|
|
1134
|
-
}).
|
|
1180
|
+
}).prefault({ memory: true, analytics: true, audit: true })
|
|
1135
1181
|
}).optional();
|
|
1136
1182
|
ConventionsConfigSchema = z2.object({
|
|
1137
1183
|
claudeDirName: z2.string().default(".claude").refine(
|
|
@@ -1162,7 +1208,7 @@ var init_config = __esm({
|
|
|
1162
1208
|
"session-state",
|
|
1163
1209
|
"agents"
|
|
1164
1210
|
]),
|
|
1165
|
-
knowledgeSourceFiles: z2.array(z2.string()).default([
|
|
1211
|
+
knowledgeSourceFiles: z2.array(z2.string()).default([...DEFAULT_KNOWLEDGE_SOURCE_FILES]),
|
|
1166
1212
|
excludePatterns: z2.array(z2.string()).default(["/ARCHIVE/", "/SESSION-HISTORY/"])
|
|
1167
1213
|
}).optional();
|
|
1168
1214
|
PythonDomainConfigSchema = z2.object({
|
|
@@ -1178,6 +1224,23 @@ var init_config = __esm({
|
|
|
1178
1224
|
}).optional();
|
|
1179
1225
|
PathsConfigSchema = z2.object({
|
|
1180
1226
|
source: z2.string().default("src"),
|
|
1227
|
+
/**
|
|
1228
|
+
* Where this stack's tests live, relative to the project root.
|
|
1229
|
+
*
|
|
1230
|
+
* Added 2026-09-20 for P4.3 of docs/plans/2026-09-20-stack-conformant-asset-rendering.md.
|
|
1231
|
+
* ~25 shipped assets told every customer their tests belong in
|
|
1232
|
+
* `packages/core/src/__tests__/` — massu's OWN layout — and no vocabulary could express
|
|
1233
|
+
* the alternative because none existed. A test directory is a first-class property of
|
|
1234
|
+
* every stack (`tests/` on FastAPI, `test/` on Rails, `src/test/java` on Spring,
|
|
1235
|
+
* `Tests/` on SwiftPM, and Go keeps `_test.go` beside the source), so it belongs here
|
|
1236
|
+
* rather than in prose.
|
|
1237
|
+
*
|
|
1238
|
+
* OPTIONAL, so every config written before this key existed keeps loading unchanged; an
|
|
1239
|
+
* asset that needs it uses `{{paths.tests | default("...")}}`. All 13 shipped templates
|
|
1240
|
+
* declare it, each with the convention it follows recorded at the site (G32 option 2),
|
|
1241
|
+
* and `stack-template-conformance.test.ts` asserts that.
|
|
1242
|
+
*/
|
|
1243
|
+
tests: z2.string().optional(),
|
|
1181
1244
|
aliases: z2.record(z2.string(), z2.string()).default({ "@": "src" }),
|
|
1182
1245
|
monorepo_roots: z2.array(z2.string()).optional(),
|
|
1183
1246
|
routers: z2.string().optional(),
|
|
@@ -1190,6 +1253,13 @@ var init_config = __esm({
|
|
|
1190
1253
|
});
|
|
1191
1254
|
LanguageFrameworkEntrySchema = z2.object({
|
|
1192
1255
|
framework: z2.string().optional(),
|
|
1256
|
+
// `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
|
|
1257
|
+
// by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
|
|
1258
|
+
// `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
|
|
1259
|
+
// derives the index builders' candidate set from it, so it is typed here rather than
|
|
1260
|
+
// read as an unknown: a consumer that has to shape-check its own SoT is one branch away
|
|
1261
|
+
// from silently contributing `undefined` to a path predicate.
|
|
1262
|
+
source_dirs: z2.array(z2.string()).optional(),
|
|
1193
1263
|
test_framework: z2.string().optional(),
|
|
1194
1264
|
test: z2.string().optional(),
|
|
1195
1265
|
runtime: z2.string().optional(),
|
|
@@ -1280,14 +1350,14 @@ var init_config = __esm({
|
|
|
1280
1350
|
project: z2.object({
|
|
1281
1351
|
name: z2.string().default("my-project"),
|
|
1282
1352
|
root: z2.string().default("auto")
|
|
1283
|
-
}).
|
|
1284
|
-
framework: FrameworkConfigSchema.
|
|
1353
|
+
}).prefault({ name: "my-project", root: "auto" }),
|
|
1354
|
+
framework: FrameworkConfigSchema.prefault({
|
|
1285
1355
|
type: "typescript",
|
|
1286
1356
|
router: "none",
|
|
1287
1357
|
orm: "none",
|
|
1288
1358
|
ui: "none"
|
|
1289
1359
|
}),
|
|
1290
|
-
paths: PathsConfigSchema.
|
|
1360
|
+
paths: PathsConfigSchema.prefault({ source: "src", aliases: { "@": "src" } }),
|
|
1291
1361
|
toolPrefix: z2.string().default("massu"),
|
|
1292
1362
|
dbAccessPattern: z2.string().optional(),
|
|
1293
1363
|
knownMismatches: z2.record(z2.string(), z2.record(z2.string(), z2.string())).optional(),
|
|
@@ -12515,7 +12585,11 @@ function defaultsFor(language, fw, dir) {
|
|
|
12515
12585
|
test: testFw === "unittest" ? prefix(dir, "python3 -m unittest") : prefix(dir, "python3 -m pytest -q"),
|
|
12516
12586
|
type: prefix(dir, "python3 -m mypy ."),
|
|
12517
12587
|
build: null,
|
|
12518
|
-
|
|
12588
|
+
// `python3 -m py_compile` with NO filenames exits 2 ('the following arguments are
|
|
12589
|
+
// required: filenames') — a check that can never pass, i.e. a brick, and a brick
|
|
12590
|
+
// gets bypassed. `compileall -q .` measured exit 1 on a tree with a syntax error
|
|
12591
|
+
// and exit 0 on a clean tree (2026-09-20).
|
|
12592
|
+
syntax: prefix(dir, "python3 -m compileall -q ."),
|
|
12519
12593
|
lint: prefix(dir, "python3 -m ruff check .")
|
|
12520
12594
|
};
|
|
12521
12595
|
}
|
|
@@ -12553,7 +12627,12 @@ function defaultsFor(language, fw, dir) {
|
|
|
12553
12627
|
return {
|
|
12554
12628
|
test: prefix(dir, "swift test"),
|
|
12555
12629
|
type: prefix(dir, "swift build"),
|
|
12556
|
-
build
|
|
12630
|
+
// `xcodebuild build` requires an .xcodeproj/.xcworkspace, but this block's other
|
|
12631
|
+
// commands (`swift test`, `swift build`) and the swift-ios template's declared
|
|
12632
|
+
// layout (Sources/) are SwiftPM. One stack cannot be SwiftPM for two keys and
|
|
12633
|
+
// Xcode for a third. Operator-approved 2026-09-20; the three anti-vacuity plants
|
|
12634
|
+
// that target this literal were re-pointed in the same change.
|
|
12635
|
+
build: prefix(dir, "swift build -c release"),
|
|
12557
12636
|
syntax: null,
|
|
12558
12637
|
lint: prefix(dir, "swiftlint")
|
|
12559
12638
|
};
|
|
@@ -12578,10 +12657,17 @@ function defaultsFor(language, fw, dir) {
|
|
|
12578
12657
|
}
|
|
12579
12658
|
case "ruby": {
|
|
12580
12659
|
return {
|
|
12581
|
-
test
|
|
12660
|
+
// Minitest is Rails' default; `bin/rails test` works on a fresh `rails new`.
|
|
12661
|
+
// `bundle exec rspec` requires adding rspec-rails to the Gemfile first.
|
|
12662
|
+
// https://guides.rubyonrails.org/testing.html
|
|
12663
|
+
test: prefix(dir, "bin/rails test"),
|
|
12582
12664
|
type: null,
|
|
12583
12665
|
build: null,
|
|
12584
|
-
|
|
12666
|
+
// `ruby -c` with NO file argument reads STDIN: on empty stdin it prints
|
|
12667
|
+
// 'Syntax OK' and exits 0 without inspecting one project file, and it BLOCKS on a
|
|
12668
|
+
// TTY. A check that cannot fail is a blind gate. Measured 2026-09-20; the
|
|
12669
|
+
// replacement exits 1 on a tree with a syntax error and 0 on a clean one.
|
|
12670
|
+
syntax: prefix(dir, "find app lib -name '*.rb' -print0 | xargs -0 -n1 ruby -c > /dev/null"),
|
|
12585
12671
|
lint: prefix(dir, "bundle exec rubocop")
|
|
12586
12672
|
};
|
|
12587
12673
|
}
|
|
@@ -13573,32 +13659,7 @@ function verifyPromotionEnvelope(payload) {
|
|
|
13573
13659
|
|
|
13574
13660
|
// src/rule-candidate-applier.ts
|
|
13575
13661
|
init_safe_write();
|
|
13576
|
-
|
|
13577
|
-
// src/lib/corrections-md.ts
|
|
13578
|
-
var CORRECTION_RULE_BULLET = /^-\s+\*\*Rule\*\*:\s*(.+)$/;
|
|
13579
|
-
function parseCorrectionRules(content) {
|
|
13580
|
-
const rules = [];
|
|
13581
|
-
for (const raw of content.split("\n")) {
|
|
13582
|
-
const line = raw.trim();
|
|
13583
|
-
const m = line.match(CORRECTION_RULE_BULLET);
|
|
13584
|
-
if (m && m[1].trim()) {
|
|
13585
|
-
rules.push(m[1].trim());
|
|
13586
|
-
continue;
|
|
13587
|
-
}
|
|
13588
|
-
if (line.startsWith("|") && line.endsWith("|")) {
|
|
13589
|
-
const cells = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
13590
|
-
if (cells.length < 4) continue;
|
|
13591
|
-
if (cells[0] === "Date" || cells[0].startsWith("-")) continue;
|
|
13592
|
-
const prevention = cells[3];
|
|
13593
|
-
if (prevention && !prevention.startsWith("-") && !prevention.startsWith("<!--")) {
|
|
13594
|
-
rules.push(prevention);
|
|
13595
|
-
}
|
|
13596
|
-
}
|
|
13597
|
-
}
|
|
13598
|
-
return rules;
|
|
13599
|
-
}
|
|
13600
|
-
|
|
13601
|
-
// src/rule-candidate-applier.ts
|
|
13662
|
+
init_corrections_md();
|
|
13602
13663
|
init_memory_authorship();
|
|
13603
13664
|
init_memory_index_region();
|
|
13604
13665
|
init_config();
|
|
@@ -13896,9 +13957,10 @@ function failSync(db, reason) {
|
|
|
13896
13957
|
// src/hooks/session-start.ts
|
|
13897
13958
|
init_memory_file_ingest();
|
|
13898
13959
|
init_config();
|
|
13960
|
+
init_corrections_md();
|
|
13899
13961
|
init_db_driver();
|
|
13900
13962
|
import { readFileSync as readFileSync19, existsSync as existsSync24 } from "fs";
|
|
13901
|
-
import {
|
|
13963
|
+
import { resolve as resolve9 } from "path";
|
|
13902
13964
|
import { parse as parseYaml4 } from "yaml";
|
|
13903
13965
|
|
|
13904
13966
|
// src/capability-advisor.ts
|
|
@@ -14188,6 +14250,20 @@ function checkWindows(pid) {
|
|
|
14188
14250
|
|
|
14189
14251
|
// src/hooks/session-start.ts
|
|
14190
14252
|
init_hook_failure_signal();
|
|
14253
|
+
|
|
14254
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
14255
|
+
import { pathToFileURL } from "url";
|
|
14256
|
+
function isDirectInvocation(moduleUrl) {
|
|
14257
|
+
const entry = process.argv[1];
|
|
14258
|
+
if (!entry) return false;
|
|
14259
|
+
try {
|
|
14260
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
14261
|
+
} catch {
|
|
14262
|
+
return false;
|
|
14263
|
+
}
|
|
14264
|
+
}
|
|
14265
|
+
|
|
14266
|
+
// src/hooks/session-start.ts
|
|
14191
14267
|
async function main() {
|
|
14192
14268
|
try {
|
|
14193
14269
|
const input = await readStdin();
|
|
@@ -14492,9 +14568,9 @@ function getMemoryDir() {
|
|
|
14492
14568
|
}
|
|
14493
14569
|
function loadCorrectionsPreventionRules() {
|
|
14494
14570
|
try {
|
|
14495
|
-
const
|
|
14496
|
-
if (!existsSync24(
|
|
14497
|
-
return parseCorrectionRules(readFileSync19(
|
|
14571
|
+
const correctionsFile = correctionsPath(getResolvedPaths().memoryDir);
|
|
14572
|
+
if (!existsSync24(correctionsFile)) return [];
|
|
14573
|
+
return parseCorrectionRules(readFileSync19(correctionsFile, "utf-8"));
|
|
14498
14574
|
} catch (_e) {
|
|
14499
14575
|
return [];
|
|
14500
14576
|
}
|
|
@@ -14546,7 +14622,9 @@ function formatAge(ms) {
|
|
|
14546
14622
|
const hr = Math.round(min / 60);
|
|
14547
14623
|
return `${hr}h`;
|
|
14548
14624
|
}
|
|
14549
|
-
|
|
14625
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
14626
|
+
main();
|
|
14627
|
+
}
|
|
14550
14628
|
/*! Bundled license information:
|
|
14551
14629
|
|
|
14552
14630
|
is-extglob/index.js:
|
|
@@ -574,6 +574,15 @@ var init_memory_path = __esm({
|
|
|
574
574
|
}
|
|
575
575
|
});
|
|
576
576
|
|
|
577
|
+
// src/lib/corrections-md.ts
|
|
578
|
+
var CORRECTIONS_FILENAME;
|
|
579
|
+
var init_corrections_md = __esm({
|
|
580
|
+
"src/lib/corrections-md.ts"() {
|
|
581
|
+
"use strict";
|
|
582
|
+
CORRECTIONS_FILENAME = "corrections.md";
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
|
|
577
586
|
// src/config-memory-schema.ts
|
|
578
587
|
import { z } from "zod";
|
|
579
588
|
var MemoryConfigSchema;
|
|
@@ -607,7 +616,7 @@ var init_config_memory_schema = __esm({
|
|
|
607
616
|
message: "memory.recall.embedEndpoint must be an http(s) URL"
|
|
608
617
|
}).optional(),
|
|
609
618
|
embedModel: z.string().min(1).optional()
|
|
610
|
-
}).
|
|
619
|
+
}).prefault({}),
|
|
611
620
|
// --- Contradiction / supersede gate (plan-living-memory-slice-2-temporal-model, P5-001) ---
|
|
612
621
|
// When a new high-value memory (decision/correction) is written, find
|
|
613
622
|
// semantically-related existing records and, if the new one contradicts an
|
|
@@ -636,7 +645,7 @@ var init_config_memory_schema = __esm({
|
|
|
636
645
|
annotateSuperseded: z.boolean().default(false),
|
|
637
646
|
// Time budget (ms) for the contradiction check; exceeded → fail-open (ADD).
|
|
638
647
|
budgetMs: z.number().int().positive().default(800)
|
|
639
|
-
}).
|
|
648
|
+
}).prefault({}),
|
|
640
649
|
// --- Background consolidation, the "sleep-time" pass
|
|
641
650
|
// (plan-living-memory-slice-3-consolidation) ---
|
|
642
651
|
// Keeps memory sharp over years: dedupes, distills dying sessions into
|
|
@@ -682,7 +691,7 @@ var init_config_memory_schema = __esm({
|
|
|
682
691
|
// Surface optional upgrades (e.g. "a local model was detected") in chat.
|
|
683
692
|
suggestUpgrades: z.boolean().default(true),
|
|
684
693
|
suggestIntervalDays: z.number().int().positive().default(30)
|
|
685
|
-
}).
|
|
694
|
+
}).prefault({}),
|
|
686
695
|
// A-20 (Slice 4) — memory FILES: the mirror, and the one switch that decides whether
|
|
687
696
|
// Massu may ever WRITE into the user's memory directory.
|
|
688
697
|
//
|
|
@@ -707,7 +716,7 @@ var init_config_memory_schema = __esm({
|
|
|
707
716
|
// turn of EVERY session, so an unbounded index is a permanent context tax — and the
|
|
708
717
|
// per-session cap bounds only the RATE, never the total.
|
|
709
718
|
indexMaxLines: z.number().int().min(1).default(50)
|
|
710
|
-
}).
|
|
719
|
+
}).prefault({}),
|
|
711
720
|
// --- Cross-repo memory surfacing (plan-living-memory-slice-5-cross-repo-surfacing) ---
|
|
712
721
|
// A decision made in one of your repos can surface in another — opt-in per
|
|
713
722
|
// decision AND opt-in per repo, signed, verified, and materialized ONLY on
|
|
@@ -732,8 +741,8 @@ var init_config_memory_schema = __esm({
|
|
|
732
741
|
enabled: z.boolean().default(true),
|
|
733
742
|
maxCrossRepoItems: z.number().int().min(0).default(1),
|
|
734
743
|
minScore: z.number().optional()
|
|
735
|
-
}).
|
|
736
|
-
}).
|
|
744
|
+
}).prefault({})
|
|
745
|
+
}).prefault({})
|
|
737
746
|
}).optional();
|
|
738
747
|
}
|
|
739
748
|
});
|
|
@@ -913,13 +922,19 @@ function getResolvedPaths() {
|
|
|
913
922
|
settingsLocalPath: resolve2(root, claudeDirName, "settings.local.json")
|
|
914
923
|
};
|
|
915
924
|
}
|
|
916
|
-
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;
|
|
925
|
+
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;
|
|
917
926
|
var init_config = __esm({
|
|
918
927
|
"src/config.ts"() {
|
|
919
928
|
"use strict";
|
|
920
929
|
init_memory_path();
|
|
930
|
+
init_corrections_md();
|
|
921
931
|
init_credentials();
|
|
922
932
|
init_config_memory_schema();
|
|
933
|
+
DEFAULT_KNOWLEDGE_SOURCE_FILES = [
|
|
934
|
+
"CLAUDE.md",
|
|
935
|
+
"MEMORY.md",
|
|
936
|
+
CORRECTIONS_FILENAME
|
|
937
|
+
];
|
|
923
938
|
DomainConfigSchema = z2.object({
|
|
924
939
|
name: z2.string().default("Unknown"),
|
|
925
940
|
routers: z2.array(z2.string()).default([]),
|
|
@@ -1048,34 +1063,40 @@ var init_config = __esm({
|
|
|
1048
1063
|
fixDetection: z2.object({
|
|
1049
1064
|
enabled: z2.boolean().default(true),
|
|
1050
1065
|
lookbackDays: z2.number().default(7),
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1066
|
+
// ⛔ NO DEFAULT LIST HERE, DELIBERATELY — the heuristics are the SSOT.
|
|
1067
|
+
//
|
|
1068
|
+
// This carried a hand-typed copy of all eight signal names while
|
|
1069
|
+
// `fix-detector.ts` already read `config…signals ?? FIX_HEURISTICS.map(h => h.name)`.
|
|
1070
|
+
// Because zod ALWAYS materialised the default, that fallback — the correct expression
|
|
1071
|
+
// — was unreachable, and the duplicate was the only thing in effect. Two authoring
|
|
1072
|
+
// sites for one vocabulary, and the drift is SILENT: a name listed here that no longer
|
|
1073
|
+
// exists simply never matches `enabledSignals.has(...)`, so a retired heuristic lingers
|
|
1074
|
+
// as a dead entry with no error. Retiring `method_name_correction` would have landed at
|
|
1075
|
+
// one site of two (CR-74).
|
|
1076
|
+
//
|
|
1077
|
+
// Optional, so an ABSENT value reaches the `??` and the heuristic list defines itself
|
|
1078
|
+
// (Rule 25). An explicit list in massu.config.yaml still overrides, which is the only
|
|
1079
|
+
// behaviour anyone actually configured — measured: `fix-detector.ts:267` is the sole
|
|
1080
|
+
// consumer and no config file in the repo sets it.
|
|
1081
|
+
signals: z2.array(z2.string()).optional()
|
|
1082
|
+
}).prefault({}),
|
|
1062
1083
|
failureClassification: z2.object({
|
|
1063
1084
|
enabled: z2.boolean().default(true),
|
|
1064
1085
|
thresholds: z2.object({
|
|
1065
1086
|
known: z2.number().default(5),
|
|
1066
1087
|
similar: z2.number().default(3)
|
|
1067
|
-
}).
|
|
1088
|
+
}).prefault({}),
|
|
1068
1089
|
scoring: z2.object({
|
|
1069
1090
|
diffPatternWeight: z2.number().default(3),
|
|
1070
1091
|
filePatternWeight: z2.number().default(2),
|
|
1071
1092
|
promptKeywordWeight: z2.number().default(2)
|
|
1072
|
-
}).
|
|
1073
|
-
}).
|
|
1093
|
+
}).prefault({})
|
|
1094
|
+
}).prefault({}),
|
|
1074
1095
|
pipeline: z2.object({
|
|
1075
1096
|
requireIncidentReport: z2.boolean().default(true),
|
|
1076
1097
|
requirePreventionRule: z2.boolean().default(true),
|
|
1077
1098
|
requireEnforcement: z2.boolean().default(true)
|
|
1078
|
-
}).
|
|
1099
|
+
}).prefault({}),
|
|
1079
1100
|
// plan-v0.2-interactive-rule-approval P-D-008 / P-D-009: project-configured
|
|
1080
1101
|
// custom destinations for the rule-candidate funnel. The classifier matches
|
|
1081
1102
|
// a candidate to one of these entries when none of the framework
|
|
@@ -1108,7 +1129,7 @@ var init_config = __esm({
|
|
|
1108
1129
|
memory: z2.boolean().default(true),
|
|
1109
1130
|
analytics: z2.boolean().default(true),
|
|
1110
1131
|
audit: z2.boolean().default(true)
|
|
1111
|
-
}).
|
|
1132
|
+
}).prefault({ memory: true, analytics: true, audit: true })
|
|
1112
1133
|
}).optional();
|
|
1113
1134
|
ConventionsConfigSchema = z2.object({
|
|
1114
1135
|
claudeDirName: z2.string().default(".claude").refine(
|
|
@@ -1139,7 +1160,7 @@ var init_config = __esm({
|
|
|
1139
1160
|
"session-state",
|
|
1140
1161
|
"agents"
|
|
1141
1162
|
]),
|
|
1142
|
-
knowledgeSourceFiles: z2.array(z2.string()).default([
|
|
1163
|
+
knowledgeSourceFiles: z2.array(z2.string()).default([...DEFAULT_KNOWLEDGE_SOURCE_FILES]),
|
|
1143
1164
|
excludePatterns: z2.array(z2.string()).default(["/ARCHIVE/", "/SESSION-HISTORY/"])
|
|
1144
1165
|
}).optional();
|
|
1145
1166
|
PythonDomainConfigSchema = z2.object({
|
|
@@ -1155,6 +1176,23 @@ var init_config = __esm({
|
|
|
1155
1176
|
}).optional();
|
|
1156
1177
|
PathsConfigSchema = z2.object({
|
|
1157
1178
|
source: z2.string().default("src"),
|
|
1179
|
+
/**
|
|
1180
|
+
* Where this stack's tests live, relative to the project root.
|
|
1181
|
+
*
|
|
1182
|
+
* Added 2026-09-20 for P4.3 of docs/plans/2026-09-20-stack-conformant-asset-rendering.md.
|
|
1183
|
+
* ~25 shipped assets told every customer their tests belong in
|
|
1184
|
+
* `packages/core/src/__tests__/` — massu's OWN layout — and no vocabulary could express
|
|
1185
|
+
* the alternative because none existed. A test directory is a first-class property of
|
|
1186
|
+
* every stack (`tests/` on FastAPI, `test/` on Rails, `src/test/java` on Spring,
|
|
1187
|
+
* `Tests/` on SwiftPM, and Go keeps `_test.go` beside the source), so it belongs here
|
|
1188
|
+
* rather than in prose.
|
|
1189
|
+
*
|
|
1190
|
+
* OPTIONAL, so every config written before this key existed keeps loading unchanged; an
|
|
1191
|
+
* asset that needs it uses `{{paths.tests | default("...")}}`. All 13 shipped templates
|
|
1192
|
+
* declare it, each with the convention it follows recorded at the site (G32 option 2),
|
|
1193
|
+
* and `stack-template-conformance.test.ts` asserts that.
|
|
1194
|
+
*/
|
|
1195
|
+
tests: z2.string().optional(),
|
|
1158
1196
|
aliases: z2.record(z2.string(), z2.string()).default({ "@": "src" }),
|
|
1159
1197
|
monorepo_roots: z2.array(z2.string()).optional(),
|
|
1160
1198
|
routers: z2.string().optional(),
|
|
@@ -1167,6 +1205,13 @@ var init_config = __esm({
|
|
|
1167
1205
|
});
|
|
1168
1206
|
LanguageFrameworkEntrySchema = z2.object({
|
|
1169
1207
|
framework: z2.string().optional(),
|
|
1208
|
+
// `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
|
|
1209
|
+
// by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
|
|
1210
|
+
// `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
|
|
1211
|
+
// derives the index builders' candidate set from it, so it is typed here rather than
|
|
1212
|
+
// read as an unknown: a consumer that has to shape-check its own SoT is one branch away
|
|
1213
|
+
// from silently contributing `undefined` to a path predicate.
|
|
1214
|
+
source_dirs: z2.array(z2.string()).optional(),
|
|
1170
1215
|
test_framework: z2.string().optional(),
|
|
1171
1216
|
test: z2.string().optional(),
|
|
1172
1217
|
runtime: z2.string().optional(),
|
|
@@ -1257,14 +1302,14 @@ var init_config = __esm({
|
|
|
1257
1302
|
project: z2.object({
|
|
1258
1303
|
name: z2.string().default("my-project"),
|
|
1259
1304
|
root: z2.string().default("auto")
|
|
1260
|
-
}).
|
|
1261
|
-
framework: FrameworkConfigSchema.
|
|
1305
|
+
}).prefault({ name: "my-project", root: "auto" }),
|
|
1306
|
+
framework: FrameworkConfigSchema.prefault({
|
|
1262
1307
|
type: "typescript",
|
|
1263
1308
|
router: "none",
|
|
1264
1309
|
orm: "none",
|
|
1265
1310
|
ui: "none"
|
|
1266
1311
|
}),
|
|
1267
|
-
paths: PathsConfigSchema.
|
|
1312
|
+
paths: PathsConfigSchema.prefault({ source: "src", aliases: { "@": "src" } }),
|
|
1268
1313
|
toolPrefix: z2.string().default("massu"),
|
|
1269
1314
|
dbAccessPattern: z2.string().optional(),
|
|
1270
1315
|
knownMismatches: z2.record(z2.string(), z2.record(z2.string(), z2.string())).optional(),
|
|
@@ -4325,6 +4370,20 @@ function entitledForTeamSharedPromotion(tier) {
|
|
|
4325
4370
|
// src/hooks/user-prompt.ts
|
|
4326
4371
|
init_rule_candidate_store();
|
|
4327
4372
|
init_hook_failure_signal();
|
|
4373
|
+
|
|
4374
|
+
// src/hooks/lib/is-direct-invocation.ts
|
|
4375
|
+
import { pathToFileURL } from "url";
|
|
4376
|
+
function isDirectInvocation(moduleUrl) {
|
|
4377
|
+
const entry = process.argv[1];
|
|
4378
|
+
if (!entry) return false;
|
|
4379
|
+
try {
|
|
4380
|
+
return moduleUrl === pathToFileURL(entry).href;
|
|
4381
|
+
} catch {
|
|
4382
|
+
return false;
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
// src/hooks/user-prompt.ts
|
|
4328
4387
|
async function main() {
|
|
4329
4388
|
try {
|
|
4330
4389
|
const input = await readStdin();
|
|
@@ -4601,4 +4660,6 @@ function readStdin() {
|
|
|
4601
4660
|
setTimeout(() => resolve5(data), 3e3);
|
|
4602
4661
|
});
|
|
4603
4662
|
}
|
|
4604
|
-
|
|
4663
|
+
if (isDirectInvocation(import.meta.url)) {
|
|
4664
|
+
main();
|
|
4665
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#### Components Needed
|
|
2
|
+
- [ ] The module that implements the feature, under `{{paths.source}}`
|
|
3
|
+
- [ ] Its public entry point, exported where this project's other modules are
|
|
4
|
+
- [ ] Registration with whatever dispatches to it (router, CLI, event handler, DI container)
|
|
5
|
+
- [ ] Input validation at the boundary
|
|
6
|
+
- [ ] Configuration, if the feature needs any
|
|
7
|
+
- [ ] Tests under `{{paths.tests | default("tests")}}`
|
|
8
|
+
- [ ] Documentation
|
|
9
|
+
|
|
10
|
+
#### Patterns to Apply (MANDATORY)
|
|
11
|
+
- [ ] Follow the registration pattern this project already uses — find the nearest
|
|
12
|
+
existing feature and mirror it rather than inventing a second shape
|
|
13
|
+
- [ ] Resolve configuration through this project's config loader, not by reading files ad hoc
|
|
14
|
+
- [ ] Release every resource you acquire (connections, handles, locks)
|
|
15
|
+
- [ ] Every error path either handles the error or propagates it — never swallows it
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#### Components Needed
|
|
2
|
+
- [ ] The module that implements the feature, under `{{paths.source}}`
|
|
3
|
+
- [ ] Its public names exported in the package `__init__.py`
|
|
4
|
+
- [ ] Registration with the application (router include, CLI entry point, task registry)
|
|
5
|
+
- [ ] Request/response models for the boundary
|
|
6
|
+
- [ ] Settings, if the feature needs configuration
|
|
7
|
+
- [ ] Tests under `{{paths.tests | default("tests")}}`
|
|
8
|
+
- [ ] Docstrings on every public function
|
|
9
|
+
|
|
10
|
+
#### Patterns to Apply (MANDATORY)
|
|
11
|
+
- [ ] Follow the registration pattern this project already uses — mirror the nearest
|
|
12
|
+
existing feature rather than inventing a second shape
|
|
13
|
+
- [ ] Type hints on every public signature; `{{verify.type | default("declare verification.LANG.type in massu.config.yaml")}}` must pass
|
|
14
|
+
- [ ] Context managers for anything that must be closed
|
|
15
|
+
- [ ] Never bare `except:` — catch the exception you mean
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#### Components Needed
|
|
2
|
+
- [ ] The module that implements the feature, under `{{paths.source}}`
|
|
3
|
+
- [ ] Its exported surface (types and functions) declared explicitly
|
|
4
|
+
- [ ] Registration with whatever dispatches to it (router, command table, DI container)
|
|
5
|
+
- [ ] Input schemas at the boundary
|
|
6
|
+
- [ ] Configuration, if the feature needs any
|
|
7
|
+
- [ ] Tests under `{{paths.tests | default("tests")}}`
|
|
8
|
+
- [ ] Documentation
|
|
9
|
+
|
|
10
|
+
#### Patterns to Apply (MANDATORY)
|
|
11
|
+
- [ ] Follow the registration pattern this project already uses — mirror the nearest
|
|
12
|
+
existing feature rather than inventing a second shape
|
|
13
|
+
- [ ] Resolve configuration through this project's config loader
|
|
14
|
+
- [ ] `{{verify.type | default("declare verification.LANG.type in massu.config.yaml")}}` must pass with no new errors
|
|
15
|
+
- [ ] Release every resource you acquire — close what you open, in a `finally`
|