@rsaglobaltech/specgate 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/README.md +8 -8
- package/bin/specgate.ts +13 -2
- package/dist/bin/specgate.js +11 -1
- package/dist/bin/specgate.js.map +1 -1
- package/dist/packages/core/src/application/GenerateAgentPromptUseCase.js +56 -0
- package/dist/packages/core/src/application/GenerateAgentPromptUseCase.js.map +1 -1
- package/dist/packages/core/src/application/ValidateProjectUseCase.js +54 -0
- package/dist/packages/core/src/application/ValidateProjectUseCase.js.map +1 -1
- package/dist/packages/core/src/domain/AdversarialProbe.js +75 -0
- package/dist/packages/core/src/domain/AdversarialProbe.js.map +1 -0
- package/dist/packages/core/src/domain/AgentPrompt.js +52 -3
- package/dist/packages/core/src/domain/AgentPrompt.js.map +1 -1
- package/dist/packages/core/src/domain/DeclaredArtifacts.js +25 -0
- package/dist/packages/core/src/domain/DeclaredArtifacts.js.map +1 -1
- package/dist/packages/core/src/domain/DoneVerification.js +68 -0
- package/dist/packages/core/src/domain/DoneVerification.js.map +1 -0
- package/dist/packages/core/src/domain/GateCommandIntegrity.js +139 -0
- package/dist/packages/core/src/domain/GateCommandIntegrity.js.map +1 -0
- package/dist/packages/core/src/domain/HarnessConfig.js +6 -0
- package/dist/packages/core/src/domain/HarnessConfig.js.map +1 -1
- package/dist/packages/core/src/domain/MultiStack.js +96 -0
- package/dist/packages/core/src/domain/MultiStack.js.map +1 -0
- package/dist/packages/core/src/domain/Precedents.js +96 -0
- package/dist/packages/core/src/domain/Precedents.js.map +1 -0
- package/dist/packages/core/src/domain/RequirementRemoval.js +161 -0
- package/dist/packages/core/src/domain/RequirementRemoval.js.map +1 -0
- package/dist/packages/core/src/domain/ScenarioCoverage.js +178 -0
- package/dist/packages/core/src/domain/ScenarioCoverage.js.map +1 -0
- package/dist/packages/core/src/domain/SpecSections.js +206 -0
- package/dist/packages/core/src/domain/SpecSections.js.map +1 -0
- package/dist/packages/core/src/domain/TestCommand.js +82 -0
- package/dist/packages/core/src/domain/TestCommand.js.map +1 -0
- package/dist/packages/core/src/domain/TraceabilityFormat.js +37 -1
- package/dist/packages/core/src/domain/TraceabilityFormat.js.map +1 -1
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js +31 -0
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js.map +1 -1
- package/dist/packages/core/test/unit/adversarial-probe.test.js +81 -0
- package/dist/packages/core/test/unit/adversarial-probe.test.js.map +1 -0
- package/dist/packages/core/test/unit/done-verification.test.js +83 -0
- package/dist/packages/core/test/unit/done-verification.test.js.map +1 -0
- package/dist/packages/core/test/unit/gate-command-integrity.test.js +114 -0
- package/dist/packages/core/test/unit/gate-command-integrity.test.js.map +1 -0
- package/dist/packages/core/test/unit/multi-stack.test.js +86 -0
- package/dist/packages/core/test/unit/multi-stack.test.js.map +1 -0
- package/dist/packages/core/test/unit/precedents.test.js +99 -0
- package/dist/packages/core/test/unit/precedents.test.js.map +1 -0
- package/dist/packages/core/test/unit/requirement-removal.test.js +124 -0
- package/dist/packages/core/test/unit/requirement-removal.test.js.map +1 -0
- package/dist/packages/core/test/unit/scenario-coverage.test.js +146 -0
- package/dist/packages/core/test/unit/scenario-coverage.test.js.map +1 -0
- package/dist/packages/core/test/unit/spec-sections.test.js +146 -0
- package/dist/packages/core/test/unit/spec-sections.test.js.map +1 -0
- package/dist/packages/core/test/unit/test-command.test.js +81 -0
- package/dist/packages/core/test/unit/test-command.test.js.map +1 -0
- package/dist/packages/core/test/unit/validate-project.test.js +21 -0
- package/dist/packages/core/test/unit/validate-project.test.js.map +1 -1
- package/dist/scripts/agents/commands.js +1 -1
- package/dist/scripts/agents/commands.js.map +1 -1
- package/dist/scripts/agents/init.js +1 -1
- package/dist/scripts/agents/init.js.map +1 -1
- package/dist/scripts/change/instructions.js +2 -2
- package/dist/scripts/change/instructions.js.map +1 -1
- package/dist/scripts/cli/commands/change/ChangeCommand.js +2 -2
- package/dist/scripts/cli/commands/change/ChangeCommand.js.map +1 -1
- package/dist/scripts/cli/commands/config/CiInitCommand.js +1 -1
- package/dist/scripts/cli/commands/config/CiInitCommand.js.map +1 -1
- package/dist/scripts/cli/commands/harness/RunCommand.js +203 -4
- package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -1
- package/dist/scripts/cli/commands/pack/InferPackCommand.js +331 -31
- package/dist/scripts/cli/commands/pack/InferPackCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/AdoptCommand.js +81 -4
- package/dist/scripts/cli/commands/project/AdoptCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/InitProjectCommand.js +245 -45
- package/dist/scripts/cli/commands/project/InitProjectCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/DoctorCommand.js +20 -2
- package/dist/scripts/cli/commands/quality/DoctorCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/FixCommand.js +1 -1
- package/dist/scripts/cli/commands/quality/FixCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/ReportCommand.js +1 -1
- package/dist/scripts/cli/commands/quality/ReportCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js +340 -18
- package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/DoneCommand.js +108 -0
- package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/PlanCommand.js +7 -1
- package/dist/scripts/cli/commands/spec/PlanCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/ReqCommand.js +112 -2
- package/dist/scripts/cli/commands/spec/ReqCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/StatusCommand.js +2 -2
- package/dist/scripts/cli/commands/spec/StatusCommand.js.map +1 -1
- package/dist/scripts/docs/build-site.js +145 -35
- package/dist/scripts/docs/build-site.js.map +1 -1
- package/dist/scripts/docs/components.js +208 -0
- package/dist/scripts/docs/components.js.map +1 -0
- package/dist/scripts/docs/highlight.js +180 -0
- package/dist/scripts/docs/highlight.js.map +1 -0
- package/dist/scripts/docs/icons.js +63 -0
- package/dist/scripts/docs/icons.js.map +1 -0
- package/dist/scripts/docs/nav.js +9 -3
- package/dist/scripts/docs/nav.js.map +1 -1
- package/dist/scripts/gen_mcp_tools.js +22 -2
- package/dist/scripts/gen_mcp_tools.js.map +1 -1
- package/dist/scripts/harness/init.js +101 -40
- package/dist/scripts/harness/init.js.map +1 -1
- package/dist/scripts/harness/worktree-lock.js +161 -0
- package/dist/scripts/harness/worktree-lock.js.map +1 -0
- package/dist/scripts/infer_pack.js +4 -1
- package/dist/scripts/infer_pack.js.map +1 -1
- package/dist/scripts/lib/surface.js +35 -7
- package/dist/scripts/lib/surface.js.map +1 -1
- package/package.json +5 -2
- package/templates/adopt/AI_RULES.md.tpl +1 -1
- package/templates/adopt/spec.md.tpl +4 -4
- package/templates/base/spec.md.tpl +12 -0
- package/templates/ci/azure.yml.tpl +2 -2
- package/templates/ci/github.yml.tpl +2 -2
- package/templates/ci/gitlab.yml.tpl +1 -1
- package/templates/ci/jenkins.tpl +1 -1
- package/templates/contracts/test-strategy.md.tpl +2 -2
- package/templates/harness/prompt-prefix.md.tpl +2 -5
|
@@ -25,13 +25,32 @@ class AgentPrompt {
|
|
|
25
25
|
`- Production artifact: ${field("technicalArtifact", "technical_artifact") || "(none declared)"}`,
|
|
26
26
|
`- Current status: ${req.status || "(none)"}`,
|
|
27
27
|
].join("\n");
|
|
28
|
+
// Before the facts, because it is the thing being implemented and the facts
|
|
29
|
+
// are only pointers to where it goes.
|
|
30
|
+
if (opts.requirementText && String(opts.requirementText).trim()) {
|
|
31
|
+
parts.push(AgentPrompt.section("The requirement (spec.md)", String(opts.requirementText).trimEnd()));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
parts.push(AgentPrompt.section("The requirement (spec.md)", `\`spec.md\` has no \`## ${req.requirement}\` section, so this prompt cannot ` +
|
|
35
|
+
`tell you what ${req.requirement} requires.\n\n` +
|
|
36
|
+
`Do not guess. Stop and report that the requirement has no text — an ` +
|
|
37
|
+
`implementation invented from a table row is worse than no implementation, ` +
|
|
38
|
+
`because the matrix will then claim it is covered.`));
|
|
39
|
+
}
|
|
28
40
|
parts.push(AgentPrompt.section("Requirement facts", facts));
|
|
29
41
|
if (opts.hint) {
|
|
30
42
|
parts.push(AgentPrompt.section("Suggested approach", opts.hint));
|
|
31
43
|
}
|
|
32
|
-
|
|
44
|
+
// `req add` writes `-` in the Feature file column by design, and this used
|
|
45
|
+
// to render it as a filename: "The feature file `-` does not exist yet.
|
|
46
|
+
// Create it." Every requirement made the documented way hit that, and an
|
|
47
|
+
// evaluator stopped rather than pay an agent to find out what a file named
|
|
48
|
+
// `-` was supposed to be.
|
|
49
|
+
const rawFeature = String(field("featureFile", "feature_file") || "")
|
|
33
50
|
.replace(/^`|`$/g, "")
|
|
34
51
|
.trim();
|
|
52
|
+
const featureRel = rawFeature === "-" ? "" : rawFeature;
|
|
53
|
+
const featureUndeclared = rawFeature === "" || rawFeature === "-";
|
|
35
54
|
if (featureRel) {
|
|
36
55
|
if (opts.featureContent) {
|
|
37
56
|
parts.push(AgentPrompt.section(`Gherkin scenario (${featureRel})`, "```gherkin\n" + opts.featureContent.trimEnd() + "\n```"));
|
|
@@ -41,9 +60,36 @@ class AgentPrompt {
|
|
|
41
60
|
`requirement before writing code.`));
|
|
42
61
|
}
|
|
43
62
|
}
|
|
63
|
+
else if (featureUndeclared) {
|
|
64
|
+
parts.push(AgentPrompt.section("Gherkin scenario", `This requirement declares no feature file — the matrix cell is empty.\n\n` +
|
|
65
|
+
`Write the scenario first, as \`features/<area>/<behaviour>.feature\`, tagged ` +
|
|
66
|
+
`\`@${req.requirement}\` and \`@${field("scenarioId", "scenario_id") || "SCN-NNN"}\` above its \`Scenario:\` line. Then record it:\n\n` +
|
|
67
|
+
` specgate req link ${req.requirement} --feature <path>\n\n` +
|
|
68
|
+
`Creating a file that does not exist is not editing the contract — the ` +
|
|
69
|
+
`rule below is about changing scenarios that are already there.`));
|
|
70
|
+
}
|
|
44
71
|
if (opts.aiRulesContent) {
|
|
45
72
|
parts.push(AgentPrompt.section("Project rules (AI_RULES.md — non-negotiable)", opts.aiRulesContent.trimEnd()));
|
|
46
73
|
}
|
|
74
|
+
if (opts.precedent && (opts.precedent.testExcerpt || opts.precedent.technicalExcerpt)) {
|
|
75
|
+
const p = opts.precedent;
|
|
76
|
+
const body = [
|
|
77
|
+
`${p.requirementId} was accepted in this same bounded context. Match its ` +
|
|
78
|
+
`structure, naming and test style — this is what "done" looks like here.`,
|
|
79
|
+
"",
|
|
80
|
+
];
|
|
81
|
+
if (p.testExcerpt) {
|
|
82
|
+
body.push(`**Its test — \`${p.testArtifact}\`**`, "", "```", p.testExcerpt, "```", "");
|
|
83
|
+
}
|
|
84
|
+
if (p.technicalExcerpt) {
|
|
85
|
+
body.push(`**Its implementation — \`${p.technicalArtifact}\`**`, "", "```", p.technicalExcerpt, "```", "");
|
|
86
|
+
}
|
|
87
|
+
// Named a precedent, not a rule: the agent must not conclude that copying
|
|
88
|
+
// it satisfies a different requirement.
|
|
89
|
+
body.push("This is an example of *shape*, not of behaviour. Your requirement is the " +
|
|
90
|
+
"one above; do not copy its assertions.");
|
|
91
|
+
parts.push(AgentPrompt.section("Precedent — an accepted requirement nearby", body.join("\n")));
|
|
92
|
+
}
|
|
47
93
|
const baseRules = opts.stageRules || [
|
|
48
94
|
"Write the test first against the technical artifact path declared in the requirement.",
|
|
49
95
|
"Run the project test command; verify the test fails for the expected reason.",
|
|
@@ -51,8 +97,11 @@ class AgentPrompt {
|
|
|
51
97
|
];
|
|
52
98
|
const doneRules = [
|
|
53
99
|
...baseRules,
|
|
54
|
-
"**Do not modify** `spec.md`, `AI_RULES.md`, or any `features/**/*.feature` —
|
|
55
|
-
|
|
100
|
+
"**Do not modify** `spec.md`, `AI_RULES.md`, or any existing `features/**/*.feature` — " +
|
|
101
|
+
"they are the contract you are being judged against. Creating a feature file that " +
|
|
102
|
+
"does not exist yet is not modifying one, and the gate allows it: an agent asked " +
|
|
103
|
+
"to write a missing scenario is not being asked to break this rule.",
|
|
104
|
+
"The harness will run `validate . --strict` and the project test command. Both must pass.",
|
|
56
105
|
];
|
|
57
106
|
parts.push(AgentPrompt.section("Definition of done", doneRules.map((r, i) => `${i + 1}. ${r}`).join("\n")));
|
|
58
107
|
if (opts.previousFailure) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentPrompt.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/AgentPrompt.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"AgentPrompt.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/AgentPrompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -34,6 +34,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
34
34
|
exports.ARTIFACT_CODES = void 0;
|
|
35
35
|
exports.looksLikePath = looksLikePath;
|
|
36
36
|
exports.declaredPaths = declaredPaths;
|
|
37
|
+
exports.artifactFile = artifactFile;
|
|
37
38
|
exports.checkDeclaredArtifacts = checkDeclaredArtifacts;
|
|
38
39
|
const Diagnostic_1 = require("./Diagnostic");
|
|
39
40
|
exports.ARTIFACT_CODES = Object.freeze({
|
|
@@ -75,6 +76,30 @@ function declaredPaths(cell) {
|
|
|
75
76
|
.filter((part) => looksLikePath(part))
|
|
76
77
|
.map((part) => part.replace(/\\/g, "/").replace(/^\.\//, ""));
|
|
77
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* The file a declared artifact names, with any selector removed.
|
|
81
|
+
*
|
|
82
|
+
* A matrix cell can point *into* a file as well as at it. Two conventions turn
|
|
83
|
+
* up: an anchor (`src/auth/login.ts#L15-L89`) and a test selector
|
|
84
|
+
* (`tests/test_config.py::test_request_dispatching`, which is pytest's, and the
|
|
85
|
+
* shape Jest and Go equivalents are usually written in too).
|
|
86
|
+
*
|
|
87
|
+
* The anchor was already stripped before checking the path exists; the selector
|
|
88
|
+
* was not. So `req link --test "tests/x.py::test_y"` was accepted and then
|
|
89
|
+
* `validate` rejected the same string as `declared_artifact_missing` — two parts
|
|
90
|
+
* of the tool disagreeing about a value one of them had just written. A cold
|
|
91
|
+
* evaluator hit it while trying to do the more precise thing, and concluded a
|
|
92
|
+
* requirement can only point at a 1500-line file.
|
|
93
|
+
*
|
|
94
|
+
* The selector stays in the cell. It is information for whoever reads the row
|
|
95
|
+
* and for the command that runs the test; it is just not part of the filename.
|
|
96
|
+
*/
|
|
97
|
+
function artifactFile(cell) {
|
|
98
|
+
return String(cell || "")
|
|
99
|
+
.split("#")[0]
|
|
100
|
+
.split("::")[0]
|
|
101
|
+
.trim();
|
|
102
|
+
}
|
|
78
103
|
/**
|
|
79
104
|
* A declared path counts as touched when the diff contains it, or contains
|
|
80
105
|
* something inside it — a row may name a directory (`src/health/`) and the work
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeclaredArtifacts.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/DeclaredArtifacts.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;;AAkBH,sCAMC;AAUD,sCAOC;AA6BD,wDAmCC;
|
|
1
|
+
{"version":3,"file":"DeclaredArtifacts.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/DeclaredArtifacts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What `done --check` has to prove before it flips a row (Fase 1.1).
|
|
4
|
+
*
|
|
5
|
+
* ## What it was
|
|
6
|
+
*
|
|
7
|
+
* `--check` and `--strict` were parsed into `DoneOptions` and then never read.
|
|
8
|
+
* `done REQ-001 --check` was a no-op that printed a tick — measured on a matrix
|
|
9
|
+
* pointing at files that do not exist, where `validate --strict-links` exits 1:
|
|
10
|
+
*
|
|
11
|
+
* ✔ REQ-001 → Implemented (1 row updated) exit=0
|
|
12
|
+
*
|
|
13
|
+
* Four documentation pages say `--check` "validates first". It validated
|
|
14
|
+
* nothing. That is the shape this repository keeps finding in itself: something
|
|
15
|
+
* that claims more than it does.
|
|
16
|
+
*
|
|
17
|
+
* ## Why the test command matters here specifically
|
|
18
|
+
*
|
|
19
|
+
* A cold evaluator's summary of the product was "nothing ever runs a test", and
|
|
20
|
+
* they were right: only the harness executes a test command, so a team on the
|
|
21
|
+
* documented path never runs one. `validate` is a static checker — it reads
|
|
22
|
+
* markdown and stats files. Marking a requirement Implemented on the strength
|
|
23
|
+
* of a static check is how "specs as executable contracts" becomes a folder of
|
|
24
|
+
* markdown and a badge.
|
|
25
|
+
*
|
|
26
|
+
* ## The rule about not knowing
|
|
27
|
+
*
|
|
28
|
+
* A project with no test command configured is not a project that passed. It is
|
|
29
|
+
* a project nothing ran, and the two have to look different — otherwise the
|
|
30
|
+
* silent case is the flattering one, which is the bias this whole tool exists to
|
|
31
|
+
* remove.
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.NO_TEST_COMMAND_WARNING = void 0;
|
|
35
|
+
exports.planDoneVerification = planDoneVerification;
|
|
36
|
+
/**
|
|
37
|
+
* The checks `done` should run, in the order they should run.
|
|
38
|
+
*
|
|
39
|
+
* `validate` comes first because it is fast and its failures are the ones with
|
|
40
|
+
* a fix line attached; running a whole test suite to then reject the row for a
|
|
41
|
+
* missing matrix header wastes the slowest thing in the loop.
|
|
42
|
+
*/
|
|
43
|
+
function planDoneVerification(projectDir, opts) {
|
|
44
|
+
if (!opts.check && !opts.strict)
|
|
45
|
+
return { steps: [], testsUnverified: false };
|
|
46
|
+
const validateArgs = [projectDir];
|
|
47
|
+
if (opts.strict) {
|
|
48
|
+
// `--strict` means "the gate this project can actually give me". Anything
|
|
49
|
+
// weaker recreates the defect a cold evaluator named: following the
|
|
50
|
+
// documentation leaves you with a weaker gate than the tool supports.
|
|
51
|
+
validateArgs.push("--strict-tdd", "--strict-links", "--strict-coverage");
|
|
52
|
+
}
|
|
53
|
+
const steps = [{ stage: "validate", argv: validateArgs }];
|
|
54
|
+
const testCmd = (opts.testCmd || "").trim();
|
|
55
|
+
if (testCmd !== "")
|
|
56
|
+
steps.push({ stage: "tests", argv: [testCmd] });
|
|
57
|
+
return { steps, testsUnverified: testCmd === "" };
|
|
58
|
+
}
|
|
59
|
+
/** The line `done` prints when nothing ran the project's tests. */
|
|
60
|
+
exports.NO_TEST_COMMAND_WARNING = Object.freeze({
|
|
61
|
+
message: "No test command configured — this checked the specification, not the code.",
|
|
62
|
+
fix: [
|
|
63
|
+
'Pass --test-cmd "<command>", or set test_cmd: in harness.config.yaml so',
|
|
64
|
+
"every `done --check` runs it. A requirement marked Implemented on a static",
|
|
65
|
+
"check alone is a claim nothing executed.",
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
//# sourceMappingURL=DoneVerification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DoneVerification.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/DoneVerification.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;;AAmCH,oDAoBC;AA3BD;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAClC,UAAkB,EAClB,IAAsB;IAEtB,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;IAE9E,MAAM,YAAY,GAAG,CAAC,UAAU,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,0EAA0E;QAC1E,oEAAoE;QACpE,sEAAsE;QACtE,YAAY,CAAC,IAAI,CAAC,cAAc,EAAE,gBAAgB,EAAE,mBAAmB,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,KAAK,GAAuB,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;IAE9E,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,IAAI,OAAO,KAAK,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAEpE,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,OAAO,KAAK,EAAE,EAAE,CAAC;AACpD,CAAC;AAED,mEAAmE;AACtD,QAAA,uBAAuB,GAAG,MAAM,CAAC,MAAM,CAAC;IACnD,OAAO,EAAE,4EAA4E;IACrF,GAAG,EAAE;QACH,yEAAyE;QACzE,4EAA4E;QAC5E,0CAA0C;KAC3C;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The gate must mean the same thing after the attempt as before it (#167).
|
|
4
|
+
*
|
|
5
|
+
* `WriteScope` protects the contract: spec.md, AI_RULES.md, features/**,
|
|
6
|
+
* docs/specs/**. It does not protect the file that defines the command the gate
|
|
7
|
+
* *runs*, and that turns out to be the same hole one level up. Measured, with a
|
|
8
|
+
* stub agent that writes no implementation at all:
|
|
9
|
+
*
|
|
10
|
+
* "scripts": { "test": "node --test tests/*.test.js" } → "echo 'all good'"
|
|
11
|
+
*
|
|
12
|
+
* over a suite that was red before it ran. The harness reported
|
|
13
|
+
* `✅ REQ-001 pass (1 attempt)`. An agent that cannot pass the test can weaken
|
|
14
|
+
* the test.
|
|
15
|
+
*
|
|
16
|
+
* ## Why this is not "protect package.json"
|
|
17
|
+
*
|
|
18
|
+
* Adding a dependency during implementation is legitimate and common, and a
|
|
19
|
+
* guard that blocks it would be turned off within a day — at which point it
|
|
20
|
+
* protects nothing. So this does not watch the file. It resolves which *script*
|
|
21
|
+
* the gate command actually invokes and watches only that one value.
|
|
22
|
+
*
|
|
23
|
+
* ## Why it declares what it cannot see
|
|
24
|
+
*
|
|
25
|
+
* `npm test` and `make check` name a script this can find. `mvn -B test` and
|
|
26
|
+
* `pytest` do not — their behaviour lives in a plugin graph or a config file
|
|
27
|
+
* this would have to interpret, and a guard that guesses is worse than one that
|
|
28
|
+
* says it did not look. `resolveGateScript` returns `null` there, and the
|
|
29
|
+
* caller reports the check as not applicable rather than as passed.
|
|
30
|
+
*/
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.resolveGateScript = resolveGateScript;
|
|
33
|
+
exports.npmScriptValue = npmScriptValue;
|
|
34
|
+
exports.makeTargetRecipe = makeTargetRecipe;
|
|
35
|
+
exports.gateCommandIntegrity = gateCommandIntegrity;
|
|
36
|
+
const NPM_RUNNERS = ["npm", "yarn", "pnpm", "bun"];
|
|
37
|
+
/**
|
|
38
|
+
* Which script definition, if any, a gate command depends on.
|
|
39
|
+
*
|
|
40
|
+
* Returns `null` when the command's meaning does not live in a file this can
|
|
41
|
+
* read — which is a real answer, not a failure.
|
|
42
|
+
*/
|
|
43
|
+
function resolveGateScript(testCmd) {
|
|
44
|
+
const tokens = String(testCmd || "")
|
|
45
|
+
.trim()
|
|
46
|
+
.split(/\s+/)
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
if (tokens.length === 0)
|
|
49
|
+
return null;
|
|
50
|
+
const [runner, ...rest] = tokens;
|
|
51
|
+
if (NPM_RUNNERS.includes(runner)) {
|
|
52
|
+
// `npm test`, `npm run verify`, `yarn test`, `pnpm run check`.
|
|
53
|
+
const args = rest.filter((a) => !a.startsWith("-"));
|
|
54
|
+
if (args.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
if (args[0] === "run" || args[0] === "run-script") {
|
|
57
|
+
return args[1] ? { manifest: "package.json", script: args[1] } : null;
|
|
58
|
+
}
|
|
59
|
+
if (args[0] === "test" || args[0] === "t")
|
|
60
|
+
return { manifest: "package.json", script: "test" };
|
|
61
|
+
// `npm start`, `npm ci` and friends are not gates; anything else is a
|
|
62
|
+
// script name only if package.json says so, which the caller resolves.
|
|
63
|
+
return { manifest: "package.json", script: args[0] };
|
|
64
|
+
}
|
|
65
|
+
if (runner === "make") {
|
|
66
|
+
const target = rest.find((a) => !a.startsWith("-"));
|
|
67
|
+
return { manifest: "Makefile", script: target || "all" };
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/** The value of an npm script, or `null` when the manifest has no such script. */
|
|
72
|
+
function npmScriptValue(manifestSource, script) {
|
|
73
|
+
let parsed;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(manifestSource);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const scripts = parsed && parsed.scripts;
|
|
81
|
+
if (!scripts || typeof scripts !== "object")
|
|
82
|
+
return null;
|
|
83
|
+
const value = scripts[script];
|
|
84
|
+
return typeof value === "string" ? value : null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The recipe lines of one Make target.
|
|
88
|
+
*
|
|
89
|
+
* Deliberately literal: a target's body is every indented line under it, up to
|
|
90
|
+
* the next rule. Reproducing Make's own parser would be a bigger promise than
|
|
91
|
+
* this needs to keep.
|
|
92
|
+
*/
|
|
93
|
+
function makeTargetRecipe(manifestSource, target) {
|
|
94
|
+
const lines = String(manifestSource).replace(/\r\n/g, "\n").split("\n");
|
|
95
|
+
const head = new RegExp(`^${target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*:`);
|
|
96
|
+
const body = [];
|
|
97
|
+
let inside = false;
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
if (head.test(line)) {
|
|
100
|
+
inside = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!inside)
|
|
104
|
+
continue;
|
|
105
|
+
if (line.trim() === "")
|
|
106
|
+
continue;
|
|
107
|
+
if (!/^\s/.test(line))
|
|
108
|
+
break;
|
|
109
|
+
body.push(line.trim());
|
|
110
|
+
}
|
|
111
|
+
return inside ? body.join("\n") : null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Did the definition the gate command depends on change during the attempt?
|
|
115
|
+
*
|
|
116
|
+
* A script that did not exist before and does not exist after is unchanged. A
|
|
117
|
+
* script that *appears* is a change: inventing the target the gate names is the
|
|
118
|
+
* same move as rewriting it.
|
|
119
|
+
*/
|
|
120
|
+
function gateCommandIntegrity(testCmd, manifestBefore, manifestAfter) {
|
|
121
|
+
const script = resolveGateScript(testCmd);
|
|
122
|
+
if (!script)
|
|
123
|
+
return { checked: false, changed: false };
|
|
124
|
+
const read = (source) => {
|
|
125
|
+
if (source === null)
|
|
126
|
+
return null;
|
|
127
|
+
return script.manifest === "Makefile"
|
|
128
|
+
? makeTargetRecipe(source, script.script)
|
|
129
|
+
: npmScriptValue(source, script.script);
|
|
130
|
+
};
|
|
131
|
+
const before = read(manifestBefore);
|
|
132
|
+
const after = read(manifestAfter);
|
|
133
|
+
// The manifest carries no such script either side: nothing here defines the
|
|
134
|
+
// gate, so there is nothing to protect and saying "passed" would overstate it.
|
|
135
|
+
if (before === null && after === null)
|
|
136
|
+
return { checked: false, changed: false, script };
|
|
137
|
+
return { checked: true, changed: before !== after, script, before, after };
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=GateCommandIntegrity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GateCommandIntegrity.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/GateCommandIntegrity.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,8 @@ class HarnessConfig {
|
|
|
15
15
|
prCmd: "",
|
|
16
16
|
attemptProfiles: [],
|
|
17
17
|
reviewProfile: "",
|
|
18
|
+
adversaryProfile: "",
|
|
19
|
+
promptPrecedents: false,
|
|
18
20
|
profileAgents: {},
|
|
19
21
|
protectedPaths: [],
|
|
20
22
|
allowPaths: [],
|
|
@@ -42,6 +44,10 @@ class HarnessConfig {
|
|
|
42
44
|
prCmd: cliArgs.prCmd || file.prCmd || HarnessConfig.DEFAULT_SETTINGS.prCmd,
|
|
43
45
|
attemptProfiles: file.attemptProfiles || HarnessConfig.DEFAULT_SETTINGS.attemptProfiles,
|
|
44
46
|
reviewProfile: file.reviewProfile || HarnessConfig.DEFAULT_SETTINGS.reviewProfile,
|
|
47
|
+
adversaryProfile: "",
|
|
48
|
+
promptPrecedents: file.promptPrecedents === undefined
|
|
49
|
+
? HarnessConfig.DEFAULT_SETTINGS.promptPrecedents
|
|
50
|
+
: file.promptPrecedents === true,
|
|
45
51
|
profileAgents: file.profileAgents || HarnessConfig.DEFAULT_SETTINGS.profileAgents,
|
|
46
52
|
// Write scope is a repository decision, like the role ladder: it comes
|
|
47
53
|
// from the file only. A flag that relaxes what the agent may edit is a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HarnessConfig.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/HarnessConfig.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"HarnessConfig.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/HarnessConfig.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What `init --multi-stack` decides, with no filesystem underneath it.
|
|
4
|
+
*
|
|
5
|
+
* The shape this produces exists because of one constraint that is easy to get
|
|
6
|
+
* wrong: three stacks implementing the same product share their *requirements*
|
|
7
|
+
* and their *scenarios*, and share nothing else. A requirement is the same
|
|
8
|
+
* sentence whether Spring or Quarkus implements it, and a Gherkin scenario
|
|
9
|
+
* describes behaviour, which is the point of writing it in Gherkin. But the
|
|
10
|
+
* traceability matrix maps a requirement to the file that implements it and the
|
|
11
|
+
* file that proves it, and those are different files in every stack — so the
|
|
12
|
+
* matrix cannot be shared, and neither can AI_RULES.md, which is the rulebook
|
|
13
|
+
* for one toolchain.
|
|
14
|
+
*
|
|
15
|
+
* Sharing the first set and splitting the second is the whole design. Copying
|
|
16
|
+
* everything would be simpler and would be a lie: editing the spec in one
|
|
17
|
+
* project would leave the other two describing a different product.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.InvalidStackList = exports.PER_STACK_PATHS = exports.SHARED_PATHS = void 0;
|
|
21
|
+
exports.planStacks = planStacks;
|
|
22
|
+
exports.renderSpecopsConfig = renderSpecopsConfig;
|
|
23
|
+
exports.relativeToRoot = relativeToRoot;
|
|
24
|
+
/** Paths that are one artifact for the whole tree, held at the root. */
|
|
25
|
+
exports.SHARED_PATHS = ["spec.md", "features", "docs/specs/adr"];
|
|
26
|
+
/** Paths each stack owns outright — they name files that differ per toolchain. */
|
|
27
|
+
exports.PER_STACK_PATHS = ["AI_RULES.md", "README.md", "docs/specs/traceability.md"];
|
|
28
|
+
class InvalidStackList extends Error {
|
|
29
|
+
}
|
|
30
|
+
exports.InvalidStackList = InvalidStackList;
|
|
31
|
+
/**
|
|
32
|
+
* Turn `--multi-stack spring,quarkus,micronaut` into the plan.
|
|
33
|
+
*
|
|
34
|
+
* Each name becomes a directory, so it has to survive being one. A stack called
|
|
35
|
+
* `../etc` would write outside the root the user named, and `projects:` already
|
|
36
|
+
* accepts relative paths that nobody checks stay inside the repository (#103) —
|
|
37
|
+
* no reason to open a second door to the same place.
|
|
38
|
+
*/
|
|
39
|
+
function planStacks(raw) {
|
|
40
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
41
|
+
throw new InvalidStackList("--multi-stack needs a comma-separated list, e.g. spring,quarkus");
|
|
42
|
+
}
|
|
43
|
+
const names = raw
|
|
44
|
+
.split(",")
|
|
45
|
+
.map((s) => s.trim())
|
|
46
|
+
.filter((s) => s !== "");
|
|
47
|
+
if (names.length < 2) {
|
|
48
|
+
throw new InvalidStackList(`--multi-stack needs at least two stacks (got ${names.length === 1 ? `'${names[0]}'` : "none"}) — ` +
|
|
49
|
+
"for one stack, plain `specgate init` is the command");
|
|
50
|
+
}
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(name)) {
|
|
54
|
+
throw new InvalidStackList(`'${name}' is not usable as a directory name — use letters, digits, dot, dash or underscore`);
|
|
55
|
+
}
|
|
56
|
+
if (name === "." || name === ".." || name.includes("..")) {
|
|
57
|
+
throw new InvalidStackList(`'${name}' would write outside the project root`);
|
|
58
|
+
}
|
|
59
|
+
const key = name.toLowerCase();
|
|
60
|
+
if (seen.has(key)) {
|
|
61
|
+
throw new InvalidStackList(`'${name}' is listed twice — each stack is one directory`);
|
|
62
|
+
}
|
|
63
|
+
seen.add(key);
|
|
64
|
+
}
|
|
65
|
+
return { stacks: names.map((name) => ({ name, entry: `./${name}` })) };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The `projects:` list `validate`, `plan`, `status` and `report` already fan out
|
|
69
|
+
* over. Written by hand rather than through a YAML library because the rest of
|
|
70
|
+
* this project reads it with `parseYamlLite`, and a writer that can emit
|
|
71
|
+
* something its own reader rejects is worse than no writer.
|
|
72
|
+
*/
|
|
73
|
+
function renderSpecopsConfig(plan, projectName) {
|
|
74
|
+
return (`# Generated by \`specgate init --multi-stack\`.\n` +
|
|
75
|
+
`#\n` +
|
|
76
|
+
`# Every entry is validated as a project in its own right. The spec and the\n` +
|
|
77
|
+
`# scenarios live once, at this root; each stack below carries its own\n` +
|
|
78
|
+
`# traceability matrix, because the files that implement and prove a\n` +
|
|
79
|
+
`# requirement are different files in every stack.\n` +
|
|
80
|
+
`name: ${projectName}\n` +
|
|
81
|
+
`projects:\n` +
|
|
82
|
+
plan.stacks.map((s) => ` - ${s.entry}\n`).join(""));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Where a shared path sits, seen from inside a stack directory.
|
|
86
|
+
*
|
|
87
|
+
* `docs/specs/adr` is two levels down, so the link back is not the same depth
|
|
88
|
+
* as the one for `spec.md`. Getting this wrong produces a link that resolves to
|
|
89
|
+
* nothing and a validate run that reports a missing required file, which is a
|
|
90
|
+
* confusing way to learn about an off-by-one.
|
|
91
|
+
*/
|
|
92
|
+
function relativeToRoot(sharedPath) {
|
|
93
|
+
const depth = sharedPath.split("/").length;
|
|
94
|
+
return "../".repeat(depth) + sharedPath;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=MultiStack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MultiStack.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/MultiStack.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Which already-accepted requirement an agent should be shown as an example (D2).
|
|
4
|
+
*
|
|
5
|
+
* ## What the prompt is missing
|
|
6
|
+
*
|
|
7
|
+
* `buildPrompt` injects the requirement's facts, its Gherkin, `AI_RULES.md` and
|
|
8
|
+
* the definition of done. Every one of those is **normative** — it says what
|
|
9
|
+
* must be true. None of them is an *example*: an agent with no conversation
|
|
10
|
+
* history has never seen what an accepted implementation looks like in this
|
|
11
|
+
* repository, so it invents a house style and the reviewer spends the next
|
|
12
|
+
* attempt correcting it.
|
|
13
|
+
*
|
|
14
|
+
* A precedent is the cheapest possible answer: here is a requirement that was
|
|
15
|
+
* accepted, here is its test, here is its production code.
|
|
16
|
+
*
|
|
17
|
+
* ## Why the same bounded context
|
|
18
|
+
*
|
|
19
|
+
* A precedent from a different part of the system is worse than none — it
|
|
20
|
+
* teaches conventions that do not apply here, with the authority of "this was
|
|
21
|
+
* accepted". D1 already derives each requirement's bounded context and records
|
|
22
|
+
* it on the trace line, so "the same context" is a question the tool can answer
|
|
23
|
+
* rather than a guess from a path.
|
|
24
|
+
*
|
|
25
|
+
* When a project has no contexts at all, every requirement shares the empty
|
|
26
|
+
* one, which is the right answer for a codebase that has not been divided yet.
|
|
27
|
+
*
|
|
28
|
+
* ## Why only Verified
|
|
29
|
+
*
|
|
30
|
+
* `Implemented` means the code exists. `Verified` means somebody checked it.
|
|
31
|
+
* Showing an agent an unverified implementation as a precedent would propagate
|
|
32
|
+
* whatever is wrong with it, and do so as an example to copy.
|
|
33
|
+
*
|
|
34
|
+
* ## Why this module reads nothing
|
|
35
|
+
*
|
|
36
|
+
* Choosing the precedent is a rule; reading the files is I/O. Keeping them
|
|
37
|
+
* apart is what lets every case below be tested without a repository.
|
|
38
|
+
*/
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.choosePrecedent = choosePrecedent;
|
|
41
|
+
exports.excerpt = excerpt;
|
|
42
|
+
/** A path cell that names nothing. The matrix uses several spellings of "no". */
|
|
43
|
+
function isPlaceholder(cell) {
|
|
44
|
+
const value = String(cell || "")
|
|
45
|
+
.replace(/`/g, "")
|
|
46
|
+
.trim();
|
|
47
|
+
return value === "" || value === "-" || value === "—" || /^tbd$/i.test(value);
|
|
48
|
+
}
|
|
49
|
+
function clean(cell) {
|
|
50
|
+
return String(cell || "")
|
|
51
|
+
.replace(/`/g, "")
|
|
52
|
+
.trim();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The most recent accepted requirement in `forRequirement`'s bounded context.
|
|
56
|
+
*
|
|
57
|
+
* "Most recent" is the highest requirement id below the current one, which is
|
|
58
|
+
* the order they were written in. Returns `null` when there is nothing worth
|
|
59
|
+
* showing — no verified sibling, or one with no artifacts to point at.
|
|
60
|
+
*/
|
|
61
|
+
function choosePrecedent(rows, contexts, forRequirement) {
|
|
62
|
+
const own = contexts[forRequirement] || "";
|
|
63
|
+
const candidates = rows
|
|
64
|
+
.filter((row) => row.requirementId && row.requirementId !== forRequirement)
|
|
65
|
+
.filter((row) => /^verified$/i.test(String(row.status || "").trim()))
|
|
66
|
+
.filter((row) => (contexts[row.requirementId] || "") === own)
|
|
67
|
+
// A precedent with neither a test nor an implementation to look at is not a
|
|
68
|
+
// precedent; it is a row.
|
|
69
|
+
.filter((row) => !isPlaceholder(row.testArtifact) || !isPlaceholder(row.technicalArtifact))
|
|
70
|
+
// Ids sort lexically the way they were written: REQ-002 before REQ-010
|
|
71
|
+
// requires the padding the format already guarantees.
|
|
72
|
+
.filter((row) => row.requirementId < forRequirement)
|
|
73
|
+
.sort((a, b) => b.requirementId.localeCompare(a.requirementId));
|
|
74
|
+
const best = candidates[0];
|
|
75
|
+
if (!best)
|
|
76
|
+
return null;
|
|
77
|
+
return {
|
|
78
|
+
requirementId: best.requirementId,
|
|
79
|
+
testArtifact: isPlaceholder(best.testArtifact) ? "" : clean(best.testArtifact),
|
|
80
|
+
technicalArtifact: isPlaceholder(best.technicalArtifact) ? "" : clean(best.technicalArtifact),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The first `lines` lines of a file's content, marked when it was cut short.
|
|
85
|
+
*
|
|
86
|
+
* A precedent is an example of shape, not a file to read: a thousand-line class
|
|
87
|
+
* in the prompt costs tokens the agent needs for its own work, and the part
|
|
88
|
+
* that teaches the convention is the top.
|
|
89
|
+
*/
|
|
90
|
+
function excerpt(content, lines = 40) {
|
|
91
|
+
const all = String(content || "").split("\n");
|
|
92
|
+
if (all.length <= lines)
|
|
93
|
+
return all.join("\n").trimEnd();
|
|
94
|
+
return `${all.slice(0, lines).join("\n").trimEnd()}\n… (${all.length - lines} more lines)`;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=Precedents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Precedents.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/Precedents.ts"],"names":[],"mappings":"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"}
|