@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
|
@@ -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(),
|
|
@@ -1287,14 +1350,14 @@ var init_config = __esm({
|
|
|
1287
1350
|
project: z2.object({
|
|
1288
1351
|
name: z2.string().default("my-project"),
|
|
1289
1352
|
root: z2.string().default("auto")
|
|
1290
|
-
}).
|
|
1291
|
-
framework: FrameworkConfigSchema.
|
|
1353
|
+
}).prefault({ name: "my-project", root: "auto" }),
|
|
1354
|
+
framework: FrameworkConfigSchema.prefault({
|
|
1292
1355
|
type: "typescript",
|
|
1293
1356
|
router: "none",
|
|
1294
1357
|
orm: "none",
|
|
1295
1358
|
ui: "none"
|
|
1296
1359
|
}),
|
|
1297
|
-
paths: PathsConfigSchema.
|
|
1360
|
+
paths: PathsConfigSchema.prefault({ source: "src", aliases: { "@": "src" } }),
|
|
1298
1361
|
toolPrefix: z2.string().default("massu"),
|
|
1299
1362
|
dbAccessPattern: z2.string().optional(),
|
|
1300
1363
|
knownMismatches: z2.record(z2.string(), z2.record(z2.string(), z2.string())).optional(),
|
|
@@ -12522,7 +12585,11 @@ function defaultsFor(language, fw, dir) {
|
|
|
12522
12585
|
test: testFw === "unittest" ? prefix(dir, "python3 -m unittest") : prefix(dir, "python3 -m pytest -q"),
|
|
12523
12586
|
type: prefix(dir, "python3 -m mypy ."),
|
|
12524
12587
|
build: null,
|
|
12525
|
-
|
|
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 ."),
|
|
12526
12593
|
lint: prefix(dir, "python3 -m ruff check .")
|
|
12527
12594
|
};
|
|
12528
12595
|
}
|
|
@@ -12560,7 +12627,12 @@ function defaultsFor(language, fw, dir) {
|
|
|
12560
12627
|
return {
|
|
12561
12628
|
test: prefix(dir, "swift test"),
|
|
12562
12629
|
type: prefix(dir, "swift build"),
|
|
12563
|
-
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"),
|
|
12564
12636
|
syntax: null,
|
|
12565
12637
|
lint: prefix(dir, "swiftlint")
|
|
12566
12638
|
};
|
|
@@ -12585,10 +12657,17 @@ function defaultsFor(language, fw, dir) {
|
|
|
12585
12657
|
}
|
|
12586
12658
|
case "ruby": {
|
|
12587
12659
|
return {
|
|
12588
|
-
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"),
|
|
12589
12664
|
type: null,
|
|
12590
12665
|
build: null,
|
|
12591
|
-
|
|
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"),
|
|
12592
12671
|
lint: prefix(dir, "bundle exec rubocop")
|
|
12593
12672
|
};
|
|
12594
12673
|
}
|
|
@@ -13580,32 +13659,7 @@ function verifyPromotionEnvelope(payload) {
|
|
|
13580
13659
|
|
|
13581
13660
|
// src/rule-candidate-applier.ts
|
|
13582
13661
|
init_safe_write();
|
|
13583
|
-
|
|
13584
|
-
// src/lib/corrections-md.ts
|
|
13585
|
-
var CORRECTION_RULE_BULLET = /^-\s+\*\*Rule\*\*:\s*(.+)$/;
|
|
13586
|
-
function parseCorrectionRules(content) {
|
|
13587
|
-
const rules = [];
|
|
13588
|
-
for (const raw of content.split("\n")) {
|
|
13589
|
-
const line = raw.trim();
|
|
13590
|
-
const m = line.match(CORRECTION_RULE_BULLET);
|
|
13591
|
-
if (m && m[1].trim()) {
|
|
13592
|
-
rules.push(m[1].trim());
|
|
13593
|
-
continue;
|
|
13594
|
-
}
|
|
13595
|
-
if (line.startsWith("|") && line.endsWith("|")) {
|
|
13596
|
-
const cells = line.split("|").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
13597
|
-
if (cells.length < 4) continue;
|
|
13598
|
-
if (cells[0] === "Date" || cells[0].startsWith("-")) continue;
|
|
13599
|
-
const prevention = cells[3];
|
|
13600
|
-
if (prevention && !prevention.startsWith("-") && !prevention.startsWith("<!--")) {
|
|
13601
|
-
rules.push(prevention);
|
|
13602
|
-
}
|
|
13603
|
-
}
|
|
13604
|
-
}
|
|
13605
|
-
return rules;
|
|
13606
|
-
}
|
|
13607
|
-
|
|
13608
|
-
// src/rule-candidate-applier.ts
|
|
13662
|
+
init_corrections_md();
|
|
13609
13663
|
init_memory_authorship();
|
|
13610
13664
|
init_memory_index_region();
|
|
13611
13665
|
init_config();
|
|
@@ -13903,9 +13957,10 @@ function failSync(db, reason) {
|
|
|
13903
13957
|
// src/hooks/session-start.ts
|
|
13904
13958
|
init_memory_file_ingest();
|
|
13905
13959
|
init_config();
|
|
13960
|
+
init_corrections_md();
|
|
13906
13961
|
init_db_driver();
|
|
13907
13962
|
import { readFileSync as readFileSync19, existsSync as existsSync24 } from "fs";
|
|
13908
|
-
import {
|
|
13963
|
+
import { resolve as resolve9 } from "path";
|
|
13909
13964
|
import { parse as parseYaml4 } from "yaml";
|
|
13910
13965
|
|
|
13911
13966
|
// src/capability-advisor.ts
|
|
@@ -14513,9 +14568,9 @@ function getMemoryDir() {
|
|
|
14513
14568
|
}
|
|
14514
14569
|
function loadCorrectionsPreventionRules() {
|
|
14515
14570
|
try {
|
|
14516
|
-
const
|
|
14517
|
-
if (!existsSync24(
|
|
14518
|
-
return parseCorrectionRules(readFileSync19(
|
|
14571
|
+
const correctionsFile = correctionsPath(getResolvedPaths().memoryDir);
|
|
14572
|
+
if (!existsSync24(correctionsFile)) return [];
|
|
14573
|
+
return parseCorrectionRules(readFileSync19(correctionsFile, "utf-8"));
|
|
14519
14574
|
} catch (_e) {
|
|
14520
14575
|
return [];
|
|
14521
14576
|
}
|
|
@@ -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(),
|
|
@@ -1264,14 +1302,14 @@ var init_config = __esm({
|
|
|
1264
1302
|
project: z2.object({
|
|
1265
1303
|
name: z2.string().default("my-project"),
|
|
1266
1304
|
root: z2.string().default("auto")
|
|
1267
|
-
}).
|
|
1268
|
-
framework: FrameworkConfigSchema.
|
|
1305
|
+
}).prefault({ name: "my-project", root: "auto" }),
|
|
1306
|
+
framework: FrameworkConfigSchema.prefault({
|
|
1269
1307
|
type: "typescript",
|
|
1270
1308
|
router: "none",
|
|
1271
1309
|
orm: "none",
|
|
1272
1310
|
ui: "none"
|
|
1273
1311
|
}),
|
|
1274
|
-
paths: PathsConfigSchema.
|
|
1312
|
+
paths: PathsConfigSchema.prefault({ source: "src", aliases: { "@": "src" } }),
|
|
1275
1313
|
toolPrefix: z2.string().default("massu"),
|
|
1276
1314
|
dbAccessPattern: z2.string().optional(),
|
|
1277
1315
|
knownMismatches: z2.record(z2.string(), z2.record(z2.string(), z2.string())).optional(),
|
|
@@ -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`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massu/core",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 73 total), 56 workflow commands, 11 agents, 20+ patterns",
|
|
6
6
|
"main": "./dist/cli.js",
|
|
@@ -44,23 +44,23 @@
|
|
|
44
44
|
"fast-glob": "^3.3.0",
|
|
45
45
|
"onnxruntime-web": "^1.27.0",
|
|
46
46
|
"proper-lockfile": "^4.1.2",
|
|
47
|
-
"smol-toml": "^1.
|
|
47
|
+
"smol-toml": "^1.8.0",
|
|
48
48
|
"tar": "^7.4.3",
|
|
49
49
|
"tweetnacl": "^1.0.3",
|
|
50
50
|
"vscode-languageserver-protocol": "^3.17.5",
|
|
51
51
|
"web-tree-sitter": "~0.25.10",
|
|
52
52
|
"yaml": "^2.4.0",
|
|
53
|
-
"zod": "^
|
|
53
|
+
"zod": "^4.6.7"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/better-sqlite3": "^7.6.13",
|
|
57
57
|
"@types/proper-lockfile": "^4.1.4",
|
|
58
|
-
"@vitest/coverage-v8": "^4.1.
|
|
58
|
+
"@vitest/coverage-v8": "^4.1.12",
|
|
59
59
|
"esbuild": "^0.28.1",
|
|
60
60
|
"semver": "^7.5.0",
|
|
61
61
|
"tsx": "^4.23.0",
|
|
62
62
|
"typescript": "^5.4.0",
|
|
63
|
-
"vitest": "^4.1.
|
|
63
|
+
"vitest": "^4.1.12"
|
|
64
64
|
},
|
|
65
65
|
"files": [
|
|
66
66
|
"dist/**/*",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"patterns/**/*",
|
|
72
72
|
"protocols/**/*",
|
|
73
73
|
"reference/**/*",
|
|
74
|
+
"fragments/**/*",
|
|
74
75
|
"templates/**/*",
|
|
75
76
|
"LICENSE",
|
|
76
77
|
"CHANGELOG.md"
|