@catspec/core 0.1.0 → 0.2.1
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/dist/index.d.ts +21 -4
- package/dist/index.js +85 -42
- package/dist/index.js.map +1 -1
- package/package.json +11 -1
- package/schemas/catspec.schema.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ declare const CODES: {
|
|
|
48
48
|
};
|
|
49
49
|
readonly CS0107: {
|
|
50
50
|
readonly severity: "error";
|
|
51
|
-
readonly title: "architecture spec
|
|
51
|
+
readonly title: "architecture spec ambiguous or not staged";
|
|
52
52
|
readonly docs: string;
|
|
53
53
|
};
|
|
54
54
|
readonly CS0108: {
|
|
@@ -2063,9 +2063,10 @@ interface SpecModel {
|
|
|
2063
2063
|
config: CatspecConfig;
|
|
2064
2064
|
specs: SpecSet;
|
|
2065
2065
|
graph: DependencyGraph;
|
|
2066
|
-
/** The architecture spec (`type: architecture` at `specs.architecture`), if exactly one exists
|
|
2066
|
+
/** The architecture spec (`type: architecture` at `specs.architecture`), if exactly one exists:
|
|
2067
|
+
* it is optional (ADR-028). */
|
|
2067
2068
|
architecture?: Spec;
|
|
2068
|
-
/** ADR ids of the architecture spec's log. */
|
|
2069
|
+
/** ADR ids of the architecture spec's log and of the plain ADR files at `specs.architecture`. */
|
|
2069
2070
|
adrIds: string[];
|
|
2070
2071
|
/** Every diagnostic of `catspec check --specs-only`, sorted. */
|
|
2071
2072
|
diagnostics: Diagnostic[];
|
|
@@ -2081,6 +2082,22 @@ interface SpecModel {
|
|
|
2081
2082
|
* `catspec check --specs-only`). Spec errors of `deprecated` specs are lowered to warnings (§D8).
|
|
2082
2083
|
*/
|
|
2083
2084
|
declare function loadSpecModel(root: string, config: CatspecConfig, source?: SourceProvider): Promise<SpecModel>;
|
|
2085
|
+
/**
|
|
2086
|
+
* Matches the files of `specs.architecture` (ADR-028): a glob or a file path as written, or a
|
|
2087
|
+
* folder (no glob characters and no `.md` ending) for every Markdown file in it.
|
|
2088
|
+
*/
|
|
2089
|
+
declare function architectureMatcher(pattern: string): (path: string) => boolean;
|
|
2090
|
+
/**
|
|
2091
|
+
* True when a file at `specs.architecture` is meant as CatSpec's architecture spec: its
|
|
2092
|
+
* frontmatter has a `catspec:` key or `type: architecture`. Other files (no frontmatter, or the
|
|
2093
|
+
* frontmatter of an ADR tool) are plain ADRs. A line match, so broken YAML is still reported.
|
|
2094
|
+
*/
|
|
2095
|
+
declare function isArchitectureSpecText(text: string): boolean;
|
|
2096
|
+
/** The ADR ids of a plain ADR file: its file name (`ADR-007-….md`, `0007-….md`) and its
|
|
2097
|
+
* `ADR-NNN` headings. */
|
|
2098
|
+
declare function plainAdrIds(file: string, text: string): string[];
|
|
2099
|
+
/** ADR ids compare by number: `ADR-7`, `ADR-007` and `ADR-0007` are the same decision. */
|
|
2100
|
+
declare const sameAdrId: (a: string, b: string) => boolean;
|
|
2084
2101
|
|
|
2085
2102
|
/**
|
|
2086
2103
|
* `status`: enforcement by spec status (§D8): draft, ready and deprecated specs aren't checked,
|
|
@@ -2706,4 +2723,4 @@ declare function formatStageRun(run: StageRun): string;
|
|
|
2706
2723
|
/** Package name. */
|
|
2707
2724
|
declare const packageName = "@catspec/core";
|
|
2708
2725
|
|
|
2709
|
-
export { type Ambiguity, type AttributeFact, BINDING_KINDS, type BaselineSymbol, type Binding, type BindingKind, type BindingResolution, BindingSchema, type BodyReference, type BuiltinGate, CODES, CONFIG_FILE, type CallFact, type CatspecConfig, type CheckMode, type CheckOptions, type CheckResult, type CodeInfo, type Codebase, type CommandRunner, ConfigError, type ConfigIssue, ConfigSchema, type ContractBullet, type CoverageReport, DependencyGraph, type Diagnostic, type DiagnosticCode, type DiagnosticCounts, type DiagnosticInit, type Driver, type DropUndefined, type EditClass, type Evidence, FRONTMATTER_KEYS, type Feature, type FeatureFolder, type FeaturePosition, type FileFacts, GATES, type Gate, type GateResult, Git, GitError, GitUnavailableError, type ImportFact, type ImportKind, type ImportedName, IndexProvider, type ItemPosition, type ItemResult, type LanguageAdapter, LineIndex, type Links, type LoadedConfig, type Location, type LockCheckOptions, type LockFile, type LockedSymbol, type Lost, MAX_SOURCE_BYTES, MEMBER_KINDS, MODIFIERS, ManualRuleSchema, type Mapping, type Member, MemberEntrySchema, type MemberKind, MemberSchema, type Modifier, type NextLockOptions, type NextStep, PLACEHOLDERS, PRESETS, PRESET_PROFILES, type ParamFact, type PatchOp, type PatchPath, type Phase, type Position, type PresetName, QueryRuleSchema, RULE_CLASSES, RULE_SCHEMAS, type Range, type Ratio, type ReadSpecOptions, type RenameInput, type RenameMap, type RenameReport, type ResolvedBinding, RevisionProvider, type Rule, type RuleClass, type RuleInput, RuleSchema, SDD_SKILLS, SKILL_DIRS, SPEC_ID_PATTERN, SPEC_STATUSES, SPEC_TYPES, SPEC_WRITING_STEP, STRUCTURAL_KINDS, SYMBOL_KINDS, type ScoredCandidate, type Severity, type Shape, type SourceEntry, type SourceFile, type SourceProvider, type Spec, type SpecBody, type SpecCoverage, type SpecEdit, SpecEditError, SpecFrontmatterSchema, type SpecModel, type SpecPatch, SpecSet, type SpecSource, type SpecStatus, type SpecType, type SplitSource, type Stage, type StageInfo, type StageRun, type StagedChange, type StagedScope, type StatusChange, type StatusGate, type StructuralRule, StructuralRuleSchema, type Suggestion, type SymbolEntry, type SymbolFact, type SymbolKind, SymbolTable, type SyncOptions, type SyncPlan, type Target, type TaskItem, TestRuleSchema, type TestRun, type Transition, type TreeEntry, type TypeRef, type Waiver, WaiverSchema, Workflow, WorkflowError, type WorkflowInput, type WorkflowOptions, type WorkflowProfile, WorkflowSchema, WorkingTreeProvider, agentSteps, applyLock, applyPatch, attributeMatches, baselineFromLock, baselineFromSymbol, bindingItems, bodyTokens, calleeNames, checkCode, classifyOp, classifyPatch, codeLocation, compareStrings, configJsonSchema, countDiagnostics, coverage, createDiagnostic, customWorkflowYaml, defaultBindingId, defaultConfig, describeStage, detectPreset, detectRenames, didYouMean, driverText, editPatch, editYaml, evaluateContract, evaluateRule, featureDirYaml, featureDirsText, featureFolderOf, finalGates, findConfig, findGitCommonDir, findGitRoot, findProgram, findSddSkills, fingerprintOf, formatLocation, formatNextStep, formatPath, formatStageRun, frontmatterLocation, gateName, gitBlobSha, globMatcher, hasLockFile, hookWorkflowDiagnostics, importTargets, inModule, isBatchScript, isEnforced, isListedPath, isRegisteredCode, isSegmentSuffix, itemHashes, jaccard, kebabCase, listFiles, loadConfig, loadSpecModel, lockPathOf, lockedFiles, memberNames, nameFreeFingerprint, nameMatches, nextLock, normalizeBinding, onPath, openCheckboxes, packageName, parseBody, parseConfig, parseErrorDiagnostics, parseLock, parseSpecSource, planSync, qualifiedNameMatches, queryDiagnostics, reached, readSpec, readYaml, relativePosix, renameScore, resolveBinding, resolveWorkflow, rewriteSymbol, runCommand, runTestCommand, serializeLock, shapeOf, signatureText, skillFile, sortDiagnostics, specHash, specJsonSchema, specLocation, splitFrontmatter, splitResolved, splitSymbol, stageLines, stageSnippet, stageStatus, stagedDiagnostics, stagedScope, statusGate, stripUndefined, testCommandArgs, toPosix, tokenSimilarity, typeMatches, typePatternMatches, unknownPlaceholders, wildcard, withFrontmatter, workflowDoctor, workflowProfile, writeStage, writesSpecs };
|
|
2726
|
+
export { type Ambiguity, type AttributeFact, BINDING_KINDS, type BaselineSymbol, type Binding, type BindingKind, type BindingResolution, BindingSchema, type BodyReference, type BuiltinGate, CODES, CONFIG_FILE, type CallFact, type CatspecConfig, type CheckMode, type CheckOptions, type CheckResult, type CodeInfo, type Codebase, type CommandRunner, ConfigError, type ConfigIssue, ConfigSchema, type ContractBullet, type CoverageReport, DependencyGraph, type Diagnostic, type DiagnosticCode, type DiagnosticCounts, type DiagnosticInit, type Driver, type DropUndefined, type EditClass, type Evidence, FRONTMATTER_KEYS, type Feature, type FeatureFolder, type FeaturePosition, type FileFacts, GATES, type Gate, type GateResult, Git, GitError, GitUnavailableError, type ImportFact, type ImportKind, type ImportedName, IndexProvider, type ItemPosition, type ItemResult, type LanguageAdapter, LineIndex, type Links, type LoadedConfig, type Location, type LockCheckOptions, type LockFile, type LockedSymbol, type Lost, MAX_SOURCE_BYTES, MEMBER_KINDS, MODIFIERS, ManualRuleSchema, type Mapping, type Member, MemberEntrySchema, type MemberKind, MemberSchema, type Modifier, type NextLockOptions, type NextStep, PLACEHOLDERS, PRESETS, PRESET_PROFILES, type ParamFact, type PatchOp, type PatchPath, type Phase, type Position, type PresetName, QueryRuleSchema, RULE_CLASSES, RULE_SCHEMAS, type Range, type Ratio, type ReadSpecOptions, type RenameInput, type RenameMap, type RenameReport, type ResolvedBinding, RevisionProvider, type Rule, type RuleClass, type RuleInput, RuleSchema, SDD_SKILLS, SKILL_DIRS, SPEC_ID_PATTERN, SPEC_STATUSES, SPEC_TYPES, SPEC_WRITING_STEP, STRUCTURAL_KINDS, SYMBOL_KINDS, type ScoredCandidate, type Severity, type Shape, type SourceEntry, type SourceFile, type SourceProvider, type Spec, type SpecBody, type SpecCoverage, type SpecEdit, SpecEditError, SpecFrontmatterSchema, type SpecModel, type SpecPatch, SpecSet, type SpecSource, type SpecStatus, type SpecType, type SplitSource, type Stage, type StageInfo, type StageRun, type StagedChange, type StagedScope, type StatusChange, type StatusGate, type StructuralRule, StructuralRuleSchema, type Suggestion, type SymbolEntry, type SymbolFact, type SymbolKind, SymbolTable, type SyncOptions, type SyncPlan, type Target, type TaskItem, TestRuleSchema, type TestRun, type Transition, type TreeEntry, type TypeRef, type Waiver, WaiverSchema, Workflow, WorkflowError, type WorkflowInput, type WorkflowOptions, type WorkflowProfile, WorkflowSchema, WorkingTreeProvider, agentSteps, applyLock, applyPatch, architectureMatcher, attributeMatches, baselineFromLock, baselineFromSymbol, bindingItems, bodyTokens, calleeNames, checkCode, classifyOp, classifyPatch, codeLocation, compareStrings, configJsonSchema, countDiagnostics, coverage, createDiagnostic, customWorkflowYaml, defaultBindingId, defaultConfig, describeStage, detectPreset, detectRenames, didYouMean, driverText, editPatch, editYaml, evaluateContract, evaluateRule, featureDirYaml, featureDirsText, featureFolderOf, finalGates, findConfig, findGitCommonDir, findGitRoot, findProgram, findSddSkills, fingerprintOf, formatLocation, formatNextStep, formatPath, formatStageRun, frontmatterLocation, gateName, gitBlobSha, globMatcher, hasLockFile, hookWorkflowDiagnostics, importTargets, inModule, isArchitectureSpecText, isBatchScript, isEnforced, isListedPath, isRegisteredCode, isSegmentSuffix, itemHashes, jaccard, kebabCase, listFiles, loadConfig, loadSpecModel, lockPathOf, lockedFiles, memberNames, nameFreeFingerprint, nameMatches, nextLock, normalizeBinding, onPath, openCheckboxes, packageName, parseBody, parseConfig, parseErrorDiagnostics, parseLock, parseSpecSource, plainAdrIds, planSync, qualifiedNameMatches, queryDiagnostics, reached, readSpec, readYaml, relativePosix, renameScore, resolveBinding, resolveWorkflow, rewriteSymbol, runCommand, runTestCommand, sameAdrId, serializeLock, shapeOf, signatureText, skillFile, sortDiagnostics, specHash, specJsonSchema, specLocation, splitFrontmatter, splitResolved, splitSymbol, stageLines, stageSnippet, stageStatus, stagedDiagnostics, stagedScope, statusGate, stripUndefined, testCommandArgs, toPosix, tokenSimilarity, typeMatches, typePatternMatches, unknownPlaceholders, wildcard, withFrontmatter, workflowDoctor, workflowProfile, writeStage, writesSpecs };
|
package/dist/index.js
CHANGED
|
@@ -32,8 +32,8 @@ var CODES = {
|
|
|
32
32
|
},
|
|
33
33
|
CS0107: {
|
|
34
34
|
severity: "error",
|
|
35
|
-
title: "architecture spec
|
|
36
|
-
docs: "
|
|
35
|
+
title: "architecture spec ambiguous or not staged",
|
|
36
|
+
docs: "The architecture spec is optional. If you have one, it is the one file at `specs.architecture` (default `docs/architecture/adr/*-adr.md`; a file, a glob or a folder) whose frontmatter has `catspec:` or `type: architecture`. More than one such file is an error: keep the repository-wide rules in one file, or narrow `specs.architecture` in `catspec.yaml`. Plain ADR files without CatSpec frontmatter are fine. In `check --staged`, an architecture spec that exists on disk but isn't staged yet is a warning that names the file to stage, so the commit isn't blocked."
|
|
37
37
|
},
|
|
38
38
|
CS0108: {
|
|
39
39
|
severity: "warning",
|
|
@@ -53,7 +53,7 @@ var CODES = {
|
|
|
53
53
|
CS0112: {
|
|
54
54
|
severity: "error",
|
|
55
55
|
title: "unknown ADR id",
|
|
56
|
-
docs: "A rule's `adr` field names a decision that isn't
|
|
56
|
+
docs: "A rule's `adr` field names a decision that isn't among your ADRs: a `## ADR-NNN: <title>` heading of the architecture spec, or a plain ADR file at `specs.architecture` (its file name, such as `0007-use-postgres.md`, or an `ADR-NNN` heading). Ids compare by number, so ADR-7 and ADR-0007 are the same. Fix the id or add the decision. Without any ADRs, `adr` isn't checked."
|
|
57
57
|
},
|
|
58
58
|
CS0130: {
|
|
59
59
|
severity: "error",
|
|
@@ -1098,7 +1098,9 @@ var ConfigSchema = z3.strictObject({
|
|
|
1098
1098
|
workflow: WorkflowSchema.prefault({}),
|
|
1099
1099
|
specs: z3.strictObject({
|
|
1100
1100
|
include: globList.default(["docs/spec/*/*.spec.md"]).describe("Machine spec files."),
|
|
1101
|
-
architecture: z3.string().min(1).default("docs/architecture/adr/*-adr.md").describe(
|
|
1101
|
+
architecture: z3.string().min(1).default("docs/architecture/adr/*-adr.md").describe(
|
|
1102
|
+
"Optional. Your ADRs: a file, a glob or a folder. At most one of these files is the architecture spec (CatSpec frontmatter, repository-wide rules); the others are plain ADRs that rules can refer to (CS0107, CS0112)."
|
|
1103
|
+
)
|
|
1102
1104
|
}).prefault({}),
|
|
1103
1105
|
languages: z3.strictObject({
|
|
1104
1106
|
typescript: language(["src/**/*.{ts,tsx}"], ["**/*.d.ts", "**/node_modules/**"]),
|
|
@@ -1167,6 +1169,9 @@ function parseConfig(text2, file = CONFIG_FILE) {
|
|
|
1167
1169
|
{ file, line: 1, column: 1, key: "", message: `the config can't be read: ${reason}` }
|
|
1168
1170
|
]);
|
|
1169
1171
|
}
|
|
1172
|
+
if (value !== null && typeof value === "object" && value.specs === null) {
|
|
1173
|
+
delete value.specs;
|
|
1174
|
+
}
|
|
1170
1175
|
const result = ConfigSchema.safeParse(value);
|
|
1171
1176
|
if (result.success) return withWorkflowDefaults(result.data, value);
|
|
1172
1177
|
throw new ConfigError(
|
|
@@ -3793,7 +3798,8 @@ var DependencyGraph = class {
|
|
|
3793
3798
|
};
|
|
3794
3799
|
|
|
3795
3800
|
// src/load.ts
|
|
3796
|
-
import {
|
|
3801
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
3802
|
+
import { join as join4, posix as posix3 } from "path";
|
|
3797
3803
|
var SpecSet = class {
|
|
3798
3804
|
byId = /* @__PURE__ */ new Map();
|
|
3799
3805
|
byFile = /* @__PURE__ */ new Map();
|
|
@@ -3822,7 +3828,7 @@ async function loadSpecModel(root, config, source = new WorkingTreeProvider(root
|
|
|
3822
3828
|
const specs = new SpecSet();
|
|
3823
3829
|
const options = { unenforcedProse: config.enforcement.unenforced_prose };
|
|
3824
3830
|
const isIncluded = globMatcher(config.specs.include);
|
|
3825
|
-
const isArchitecture =
|
|
3831
|
+
const isArchitecture = architectureMatcher(config.specs.architecture);
|
|
3826
3832
|
const architectureFiles = files.filter((f) => isArchitecture(f));
|
|
3827
3833
|
const specFiles2 = files.filter((f) => isIncluded(f) && !architectureFiles.includes(f));
|
|
3828
3834
|
const involved = /* @__PURE__ */ new Map();
|
|
@@ -3833,39 +3839,37 @@ async function loadSpecModel(root, config, source = new WorkingTreeProvider(root
|
|
|
3833
3839
|
return result.spec;
|
|
3834
3840
|
};
|
|
3835
3841
|
let architecture;
|
|
3836
|
-
const
|
|
3842
|
+
const architectureTexts = /* @__PURE__ */ new Map();
|
|
3843
|
+
for (const file of architectureFiles) {
|
|
3844
|
+
architectureTexts.set(file, (await source.read(file))?.toString("utf8") ?? "");
|
|
3845
|
+
}
|
|
3846
|
+
const rulesFiles = architectureFiles.filter(
|
|
3847
|
+
(f) => isArchitectureSpecText(architectureTexts.get(f))
|
|
3848
|
+
);
|
|
3849
|
+
const unstagedLog = rulesFiles.length === 0 && source.kind === "index" ? await findUnstagedArchitectureSpec(root, isArchitecture) : void 0;
|
|
3837
3850
|
if (unstagedLog) {
|
|
3838
3851
|
diagnostics.push(
|
|
3839
3852
|
createDiagnostic("CS0107", {
|
|
3840
3853
|
severity: "warning",
|
|
3841
|
-
message: `the
|
|
3854
|
+
message: `the architecture spec ${unstagedLog} isn't staged, so this commit is checked without its rules`,
|
|
3842
3855
|
suggestions: [{ message: `stage it: git add ${unstagedLog}` }]
|
|
3843
3856
|
})
|
|
3844
3857
|
);
|
|
3845
|
-
} else if (
|
|
3858
|
+
} else if (rulesFiles.length > 1) {
|
|
3846
3859
|
diagnostics.push(
|
|
3847
3860
|
createDiagnostic("CS0107", {
|
|
3848
|
-
|
|
3861
|
+
specLocation: { file: rulesFiles[0], line: 1, column: 1 },
|
|
3862
|
+
message: `more than one architecture spec matches specs.architecture "${config.specs.architecture}": ` + listOf(rulesFiles),
|
|
3863
|
+
actual: rulesFiles,
|
|
3849
3864
|
suggestions: [
|
|
3850
3865
|
{
|
|
3851
|
-
message: "
|
|
3866
|
+
message: "keep the repository-wide rules in one file, or narrow specs.architecture in catspec.yaml"
|
|
3852
3867
|
}
|
|
3853
3868
|
]
|
|
3854
3869
|
})
|
|
3855
3870
|
);
|
|
3856
|
-
} else if (
|
|
3857
|
-
|
|
3858
|
-
createDiagnostic("CS0107", {
|
|
3859
|
-
specLocation: { file: architectureFiles[0], line: 1, column: 1 },
|
|
3860
|
-
message: `more than one architecture spec matches specs.architecture "${config.specs.architecture}": ` + listOf(architectureFiles),
|
|
3861
|
-
actual: architectureFiles,
|
|
3862
|
-
suggestions: [
|
|
3863
|
-
{ message: "keep one ADR log, or narrow specs.architecture in catspec.yaml" }
|
|
3864
|
-
]
|
|
3865
|
-
})
|
|
3866
|
-
);
|
|
3867
|
-
} else {
|
|
3868
|
-
const spec = await read(architectureFiles[0]);
|
|
3871
|
+
} else if (rulesFiles.length === 1) {
|
|
3872
|
+
const spec = await read(rulesFiles[0]);
|
|
3869
3873
|
if (spec) {
|
|
3870
3874
|
if (spec.type === "architecture") {
|
|
3871
3875
|
architecture = spec;
|
|
@@ -3937,20 +3941,28 @@ async function loadSpecModel(root, config, source = new WorkingTreeProvider(root
|
|
|
3937
3941
|
);
|
|
3938
3942
|
}
|
|
3939
3943
|
}
|
|
3940
|
-
const adrIds = architecture?.body.adrIds ?? [];
|
|
3941
|
-
|
|
3944
|
+
const adrIds = [...architecture?.body.adrIds ?? []];
|
|
3945
|
+
for (const file of architectureFiles) {
|
|
3946
|
+
if (rulesFiles.includes(file)) continue;
|
|
3947
|
+
for (const id of plainAdrIds(file, architectureTexts.get(file))) {
|
|
3948
|
+
if (!adrIds.some((known) => sameAdrId(known, id))) adrIds.push(id);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
const adrLog = architecture?.file ?? `specs.architecture "${config.specs.architecture}"`;
|
|
3952
|
+
if (adrIds.length > 0) {
|
|
3942
3953
|
for (const spec of specs.byFile.values()) {
|
|
3943
3954
|
for (const rule of spec.rules) {
|
|
3944
|
-
|
|
3945
|
-
|
|
3955
|
+
const ruleAdr = rule.adr;
|
|
3956
|
+
if (ruleAdr === void 0 || adrIds.some((id) => sameAdrId(id, ruleAdr))) continue;
|
|
3957
|
+
const close = didYouMean(ruleAdr, adrIds);
|
|
3946
3958
|
diagnostics.push(
|
|
3947
3959
|
createDiagnostic("CS0112", {
|
|
3948
3960
|
spec: spec.id,
|
|
3949
3961
|
ruleId: rule.id,
|
|
3950
3962
|
specLocation: specLocation(spec.source, ["rules", rule.index, "adr"]),
|
|
3951
|
-
message: `rule "${rule.id}" enforces ${
|
|
3963
|
+
message: `rule "${rule.id}" enforces ${ruleAdr}, which is not in the ADR log ${adrLog}`,
|
|
3952
3964
|
expected: adrIds,
|
|
3953
|
-
actual:
|
|
3965
|
+
actual: ruleAdr,
|
|
3954
3966
|
...close.length > 0 ? { suggestions: close.map((c) => ({ message: `did you mean ${c}?` })) } : {}
|
|
3955
3967
|
})
|
|
3956
3968
|
);
|
|
@@ -4021,6 +4033,33 @@ async function loadSpecModel(root, config, source = new WorkingTreeProvider(root
|
|
|
4021
4033
|
source
|
|
4022
4034
|
};
|
|
4023
4035
|
}
|
|
4036
|
+
function architectureMatcher(pattern) {
|
|
4037
|
+
const clean = pattern.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
4038
|
+
if (/[*?[\]{}()!]/.test(clean) || /\.md$/i.test(clean)) return globMatcher(clean);
|
|
4039
|
+
return globMatcher(`${clean}/**/*.md`);
|
|
4040
|
+
}
|
|
4041
|
+
function isArchitectureSpecText(text2) {
|
|
4042
|
+
const split = splitFrontmatter(text2);
|
|
4043
|
+
if ("error" in split) return false;
|
|
4044
|
+
return /^(catspec\s*:|type\s*:\s*["']?architecture["']?\s*(#.*)?$)/m.test(split.frontmatter.text);
|
|
4045
|
+
}
|
|
4046
|
+
function plainAdrIds(file, text2) {
|
|
4047
|
+
const ids = [];
|
|
4048
|
+
const name = /^(?:adr-?)?(\d+)(?=[-_. ])/i.exec(posix3.basename(file));
|
|
4049
|
+
if (name) ids.push(`ADR-${name[1]}`);
|
|
4050
|
+
for (const match of text2.matchAll(/^#{1,6}[ \t]+\[?(ADR-\d+)\b/gm)) {
|
|
4051
|
+
if (!ids.some((id) => sameAdrId(id, match[1]))) ids.push(match[1]);
|
|
4052
|
+
}
|
|
4053
|
+
return ids;
|
|
4054
|
+
}
|
|
4055
|
+
var sameAdrId = (a, b) => Number(a.slice(4)) === Number(b.slice(4)) && /^ADR-\d+$/.test(a) && /^ADR-\d+$/.test(b);
|
|
4056
|
+
async function findUnstagedArchitectureSpec(root, isArchitecture) {
|
|
4057
|
+
for (const file of (await listFiles(root)).filter((f) => isArchitecture(f))) {
|
|
4058
|
+
const text2 = await readFile3(join4(root, file), "utf8").catch(() => "");
|
|
4059
|
+
if (isArchitectureSpecText(text2)) return file;
|
|
4060
|
+
}
|
|
4061
|
+
return void 0;
|
|
4062
|
+
}
|
|
4024
4063
|
|
|
4025
4064
|
// src/spec-editor.ts
|
|
4026
4065
|
import { isDeepStrictEqual } from "util";
|
|
@@ -5164,11 +5203,11 @@ var editPatch = (edit) => ({ file: edit.spec.file, ops: edit.ops });
|
|
|
5164
5203
|
// src/workflow/engine.ts
|
|
5165
5204
|
import { execFile as execFile3 } from "child_process";
|
|
5166
5205
|
import { appendFile, mkdir, writeFile } from "fs/promises";
|
|
5167
|
-
import { join as
|
|
5206
|
+
import { join as join6 } from "path";
|
|
5168
5207
|
|
|
5169
5208
|
// src/workflow/doctor.ts
|
|
5170
5209
|
import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
5171
|
-
import { delimiter, isAbsolute, join as
|
|
5210
|
+
import { delimiter, isAbsolute, join as join5 } from "path";
|
|
5172
5211
|
import { LineCounter as LineCounter2, parseDocument as parseDocument4 } from "yaml";
|
|
5173
5212
|
function configLocator(configFile) {
|
|
5174
5213
|
if (!configFile) return () => void 0;
|
|
@@ -5184,14 +5223,14 @@ function configLocator(configFile) {
|
|
|
5184
5223
|
}
|
|
5185
5224
|
function findProgram(name, root, env = process.env, platform = process.platform) {
|
|
5186
5225
|
if (name.includes("/") || name.includes("\\")) {
|
|
5187
|
-
const path = isAbsolute(name) ? name :
|
|
5226
|
+
const path = isAbsolute(name) ? name : join5(root, name);
|
|
5188
5227
|
return existsSync2(path) ? path : void 0;
|
|
5189
5228
|
}
|
|
5190
5229
|
const extensions = platform === "win32" ? ["", ...(env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").map((e) => e.toLowerCase())] : [""];
|
|
5191
5230
|
const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
|
|
5192
5231
|
for (const dir of dirs) {
|
|
5193
5232
|
for (const ext of extensions) {
|
|
5194
|
-
const file =
|
|
5233
|
+
const file = join5(dir, name + ext);
|
|
5195
5234
|
try {
|
|
5196
5235
|
if (statSync2(file).isFile()) return file;
|
|
5197
5236
|
} catch {
|
|
@@ -5204,7 +5243,7 @@ var onPath = (name, root, env = process.env) => findProgram(name, root, env) !==
|
|
|
5204
5243
|
var isBatchScript = (path) => /\.(cmd|bat)$/i.test(path);
|
|
5205
5244
|
var SKILL_DIRS = [".claude/skills", ".agents/skills"];
|
|
5206
5245
|
var sddSkillsIn = (root, skillsDir) => {
|
|
5207
|
-
const dir =
|
|
5246
|
+
const dir = join5(root, skillsDir);
|
|
5208
5247
|
return existsSync2(dir) ? readdirSync(dir).filter((n) => n.startsWith("sdd-")).sort() : [];
|
|
5209
5248
|
};
|
|
5210
5249
|
function findSddSkills(root) {
|
|
@@ -5215,8 +5254,8 @@ function findSddSkills(root) {
|
|
|
5215
5254
|
return void 0;
|
|
5216
5255
|
}
|
|
5217
5256
|
function detectPreset(root, skillsDir) {
|
|
5218
|
-
if (existsSync2(
|
|
5219
|
-
if (existsSync2(
|
|
5257
|
+
if (existsSync2(join5(root, "openspec"))) return "openspec";
|
|
5258
|
+
if (existsSync2(join5(root, ".specify"))) return "spec-kit";
|
|
5220
5259
|
const found = skillsDir ? sddSkillsIn(root, skillsDir).length > 0 : findSddSkills(root);
|
|
5221
5260
|
return found ? "custom" : void 0;
|
|
5222
5261
|
}
|
|
@@ -5232,7 +5271,7 @@ function workflowDoctor(root, profile, options = {}) {
|
|
|
5232
5271
|
const driver = stage2.driver;
|
|
5233
5272
|
if (driver.kind === "skill") {
|
|
5234
5273
|
const file = skillFile(profile, driver.name);
|
|
5235
|
-
if (!existsSync2(
|
|
5274
|
+
if (!existsSync2(join5(root, file))) {
|
|
5236
5275
|
diagnostics.push(
|
|
5237
5276
|
createDiagnostic("CS0704", {
|
|
5238
5277
|
...location(["stages", index, "driver", "skill"]),
|
|
@@ -5295,7 +5334,7 @@ function workflowDoctor(root, profile, options = {}) {
|
|
|
5295
5334
|
"spec-kit": ".specify/"
|
|
5296
5335
|
};
|
|
5297
5336
|
const folder = expected[profile.preset];
|
|
5298
|
-
if (folder && !existsSync2(
|
|
5337
|
+
if (folder && !existsSync2(join5(root, folder))) {
|
|
5299
5338
|
diagnostics.push(
|
|
5300
5339
|
createDiagnostic("CS0700", {
|
|
5301
5340
|
severity: "warning",
|
|
@@ -6015,11 +6054,11 @@ var describeStage = (stage2) => ({
|
|
|
6015
6054
|
}
|
|
6016
6055
|
});
|
|
6017
6056
|
async function writeStage(root, run, now = /* @__PURE__ */ new Date()) {
|
|
6018
|
-
for (const change of run.changes) await writeFile(
|
|
6057
|
+
for (const change of run.changes) await writeFile(join6(root, change.file), change.text, "utf8");
|
|
6019
6058
|
if (!run.forced) return;
|
|
6020
6059
|
const common = findGitCommonDir(root);
|
|
6021
6060
|
if (!common) return;
|
|
6022
|
-
const dir =
|
|
6061
|
+
const dir = join6(common, "catspec");
|
|
6023
6062
|
await mkdir(dir, { recursive: true });
|
|
6024
6063
|
const entry = {
|
|
6025
6064
|
at: now.toISOString(),
|
|
@@ -6028,7 +6067,7 @@ async function writeStage(root, run, now = /* @__PURE__ */ new Date()) {
|
|
|
6028
6067
|
target: run.target,
|
|
6029
6068
|
overridden: run.diagnostics.filter((d) => FORCED.has(d.code)).map((d) => `${d.code} ${d.message}`)
|
|
6030
6069
|
};
|
|
6031
|
-
await appendFile(
|
|
6070
|
+
await appendFile(join6(dir, "workflow-overrides.log"), `${JSON.stringify(entry)}
|
|
6032
6071
|
`, "utf8");
|
|
6033
6072
|
}
|
|
6034
6073
|
function runCommand(root, args, timeoutMs = 10 * 6e4, platform = process.platform, env = process.env) {
|
|
@@ -6183,6 +6222,7 @@ export {
|
|
|
6183
6222
|
agentSteps,
|
|
6184
6223
|
applyLock,
|
|
6185
6224
|
applyPatch,
|
|
6225
|
+
architectureMatcher,
|
|
6186
6226
|
attributeMatches,
|
|
6187
6227
|
baselineFromLock,
|
|
6188
6228
|
baselineFromSymbol,
|
|
@@ -6232,6 +6272,7 @@ export {
|
|
|
6232
6272
|
hookWorkflowDiagnostics,
|
|
6233
6273
|
importTargets,
|
|
6234
6274
|
inModule,
|
|
6275
|
+
isArchitectureSpecText,
|
|
6235
6276
|
isBatchScript,
|
|
6236
6277
|
isEnforced,
|
|
6237
6278
|
isListedPath,
|
|
@@ -6258,6 +6299,7 @@ export {
|
|
|
6258
6299
|
parseErrorDiagnostics,
|
|
6259
6300
|
parseLock,
|
|
6260
6301
|
parseSpecSource,
|
|
6302
|
+
plainAdrIds,
|
|
6261
6303
|
planSync,
|
|
6262
6304
|
qualifiedNameMatches,
|
|
6263
6305
|
queryDiagnostics,
|
|
@@ -6271,6 +6313,7 @@ export {
|
|
|
6271
6313
|
rewriteSymbol,
|
|
6272
6314
|
runCommand,
|
|
6273
6315
|
runTestCommand,
|
|
6316
|
+
sameAdrId,
|
|
6274
6317
|
serializeLock,
|
|
6275
6318
|
shapeOf,
|
|
6276
6319
|
signatureText,
|