@printpractical/cruze 0.0.13 → 0.0.14
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/README.md +61 -118
- package/dist/adapters/inbound/cli/cli_context.js +0 -27
- package/dist/adapters/inbound/cli/commands/code.js +5 -18
- package/dist/adapters/inbound/cli/commands/documents.js +11 -37
- package/dist/adapters/inbound/cli/commands/init.js +1 -1
- package/dist/adapters/inbound/cli/commands/review.js +2 -9
- package/dist/adapters/inbound/cli/commands/status.js +16 -63
- package/dist/adapters/inbound/cli/commands.js +10 -37
- package/dist/adapters/inbound/cli/parse_command.js +0 -54
- package/dist/adapters/outbound/git_repository.js +2 -71
- package/dist/app/ports/repository.js +0 -12
- package/dist/app/project_context.js +15 -25
- package/dist/app/use_cases/approve_document.js +29 -0
- package/dist/app/use_cases/check_code.js +21 -56
- package/dist/app/use_cases/new_document.js +25 -0
- package/dist/app/use_cases/project_status.js +43 -67
- package/dist/app/use_cases/run_review.js +11 -30
- package/dist/app/use_cases/validate_project.js +4 -4
- package/dist/domain/approvals.js +51 -0
- package/dist/domain/check/check_source.js +2 -31
- package/dist/domain/config.js +8 -84
- package/dist/domain/hashing.js +16 -30
- package/dist/domain/ids.js +13 -27
- package/dist/domain/next_step.js +41 -0
- package/dist/domain/project/features.js +71 -0
- package/dist/domain/project/layout.js +11 -27
- package/dist/domain/project/project_docs.js +48 -0
- package/dist/domain/project/release.js +30 -0
- package/dist/domain/scaffold.js +0 -1
- package/dist/domain/skill_budget.js +4 -4
- package/dist/domain/trace.js +8 -25
- package/dist/domain/validation/document_rules.js +70 -0
- package/dist/domain/validation/release_rules.js +45 -0
- package/dist/domain/validation/validate_project.js +20 -27
- package/dist/domain/versions/cruze_version.js +3 -9
- package/dist/main.js +1 -1
- package/package.json +1 -1
- package/skills/asking/SKILL.md +51 -0
- package/skills/behavioural-testing/SKILL.md +14 -16
- package/skills/behavioural-testing/examples.md +1 -2
- package/skills/behavioural-testing/fakes.md +2 -2
- package/skills/build/SKILL.md +34 -38
- package/skills/dependency-approval/SKILL.md +34 -31
- package/skills/envision/SKILL.md +23 -18
- package/skills/envision/architecture.md +16 -0
- package/skills/envision/breakdown.md +14 -0
- package/skills/envision/possibilities.md +6 -7
- package/skills/fix/SKILL.md +34 -0
- package/skills/formats/SKILL.md +39 -87
- package/skills/formats/config.md +47 -0
- package/skills/formats/templates/architecture.md +18 -74
- package/skills/formats/templates/feature.md +18 -34
- package/skills/formats/templates/vision.md +15 -27
- package/skills/hexagonal-design/SKILL.md +11 -11
- package/skills/hexagonal-design/contracts.md +3 -3
- package/skills/hexagonal-design/fundamentals.md +1 -1
- package/skills/hexagonal-design/languages/cpp.md +1 -1
- package/skills/hexagonal-design/languages/rust.md +1 -1
- package/skills/hexagonal-design/languages/swift.md +2 -2
- package/skills/hexagonal-design/runtime.md +2 -2
- package/skills/research/SKILL.md +4 -4
- package/skills/research/evaluation.md +1 -1
- package/skills/review/SKILL.md +27 -0
- package/skills/roles/SKILL.md +8 -59
- package/skills/roles/code-reviewer.md +21 -32
- package/skills/roles/researcher.md +3 -3
- package/skills/shape/SKILL.md +37 -0
- package/skills/shape/self-check.md +12 -0
- package/src/adapters/inbound/cli/cli_context.ts +0 -27
- package/src/adapters/inbound/cli/commands/code.ts +5 -18
- package/src/adapters/inbound/cli/commands/documents.ts +11 -37
- package/src/adapters/inbound/cli/commands/init.ts +1 -1
- package/src/adapters/inbound/cli/commands/review.ts +2 -9
- package/src/adapters/inbound/cli/commands/status.ts +16 -63
- package/src/adapters/inbound/cli/commands.ts +10 -37
- package/src/adapters/inbound/cli/parse_command.ts +0 -54
- package/src/adapters/outbound/git_repository.ts +2 -71
- package/src/app/ports/repository.ts +0 -12
- package/src/app/project_context.ts +15 -25
- package/src/app/use_cases/approve_document.ts +29 -0
- package/src/app/use_cases/check_code.ts +21 -56
- package/src/app/use_cases/new_document.ts +25 -0
- package/src/app/use_cases/project_status.ts +49 -73
- package/src/app/use_cases/run_review.ts +11 -30
- package/src/app/use_cases/validate_project.ts +4 -4
- package/src/domain/approvals.ts +51 -0
- package/src/domain/check/check_source.ts +2 -31
- package/src/domain/config.ts +9 -85
- package/src/domain/hashing.ts +16 -30
- package/src/domain/ids.ts +13 -27
- package/src/domain/next_step.ts +41 -0
- package/src/domain/project/features.ts +71 -0
- package/src/domain/project/layout.ts +11 -27
- package/src/domain/project/project_docs.ts +48 -0
- package/src/domain/project/release.ts +30 -0
- package/src/domain/scaffold.ts +0 -1
- package/src/domain/skill_budget.ts +4 -4
- package/src/domain/trace.ts +8 -25
- package/src/domain/validation/document_rules.ts +70 -0
- package/src/domain/validation/release_rules.ts +45 -0
- package/src/domain/validation/validate_project.ts +21 -28
- package/src/domain/versions/cruze_version.ts +3 -9
- package/src/main.ts +1 -1
- package/templates/init/AGENTS.md +4 -12
- package/templates/init/ci.yml +3 -3
- package/templates/init/config.yaml +8 -17
- package/templates/init/review/claude.yaml +2 -2
- package/dist/adapters/inbound/cli/argument_files.js +0 -29
- package/dist/adapters/inbound/cli/commands/diff.js +0 -23
- package/dist/adapters/inbound/cli/commands/journal.js +0 -32
- package/dist/adapters/inbound/cli/commands/progress.js +0 -55
- package/dist/adapters/inbound/cli/commands/realign.js +0 -28
- package/dist/adapters/inbound/cli/commands/show.js +0 -96
- package/dist/app/use_cases/abandon_work.js +0 -42
- package/dist/app/use_cases/approve_artifact.js +0 -197
- package/dist/app/use_cases/journal_events.js +0 -64
- package/dist/app/use_cases/land_change.js +0 -134
- package/dist/app/use_cases/new_project_doc.js +0 -48
- package/dist/app/use_cases/new_work_item.js +0 -75
- package/dist/app/use_cases/realign_done.js +0 -45
- package/dist/app/use_cases/realign_status.js +0 -106
- package/dist/app/use_cases/record_progress.js +0 -118
- package/dist/app/use_cases/show_elements.js +0 -18
- package/dist/app/use_cases/show_notes.js +0 -11
- package/dist/app/use_cases/show_scope.js +0 -51
- package/dist/app/use_cases/upstream_diff.js +0 -55
- package/dist/domain/approvals/agent_approval.js +0 -68
- package/dist/domain/approvals/evaluate.js +0 -36
- package/dist/domain/approvals/fingerprint.js +0 -52
- package/dist/domain/approvals/rebase.js +0 -39
- package/dist/domain/approvals/records.js +0 -50
- package/dist/domain/approvals/upstream_text.js +0 -36
- package/dist/domain/architecture_sections.js +0 -23
- package/dist/domain/check/decision_comments.js +0 -38
- package/dist/domain/decision_citations.js +0 -62
- package/dist/domain/edits/agents_file.js +0 -58
- package/dist/domain/edits/managed_tables.js +0 -60
- package/dist/domain/edits/progress_block.js +0 -33
- package/dist/domain/edits/status_lines.js +0 -52
- package/dist/domain/elements.js +0 -126
- package/dist/domain/journal.js +0 -109
- package/dist/domain/land/bookkeeping.js +0 -54
- package/dist/domain/land/merge_plan.js +0 -137
- package/dist/domain/land/text_ops.js +0 -66
- package/dist/domain/line_diff.js +0 -56
- package/dist/domain/project/artifact_ref.js +0 -42
- package/dist/domain/project/deltas.js +0 -49
- package/dist/domain/project/element_lookup.js +0 -124
- package/dist/domain/project/living_model.js +0 -55
- package/dist/domain/project/new_items.js +0 -48
- package/dist/domain/project/notes.js +0 -74
- package/dist/domain/project/plan_parts.js +0 -111
- package/dist/domain/project/progress.js +0 -43
- package/dist/domain/project/project_view.js +0 -72
- package/dist/domain/project/roadmap.js +0 -43
- package/dist/domain/project/scope_view.js +0 -134
- package/dist/domain/project/work_items.js +0 -68
- package/dist/domain/status/next_step.js +0 -181
- package/dist/domain/status/pending_rethink.js +0 -22
- package/dist/domain/status/verification.js +0 -29
- package/dist/domain/status/work_status.js +0 -125
- package/dist/domain/validation/decision_rules.js +0 -76
- package/dist/domain/validation/delta_rules.js +0 -81
- package/dist/domain/validation/element_rules.js +0 -104
- package/dist/domain/validation/future_rules.js +0 -40
- package/dist/domain/validation/living_rules.js +0 -99
- package/dist/domain/validation/plan_rules.js +0 -124
- package/dist/domain/validation/project_rules.js +0 -115
- package/dist/domain/validation/size_rules.js +0 -45
- package/dist/domain/validation/vision_rules.js +0 -41
- package/dist/domain/validation/work_item_rules.js +0 -175
- package/dist/domain/versions/realign_notes.js +0 -72
- package/dist/domain/versions/rule_versions.js +0 -29
- package/dist/domain/versions/scheduled_exceptions.js +0 -41
- package/skills/about/SKILL.md +0 -21
- package/skills/architect/SKILL.md +0 -49
- package/skills/architect/feature.md +0 -40
- package/skills/architect/project.md +0 -71
- package/skills/architect/tweak.md +0 -22
- package/skills/architect/walkthrough.md +0 -35
- package/skills/domain-language/SKILL.md +0 -37
- package/skills/envision/feature.md +0 -25
- package/skills/envision/intake.md +0 -28
- package/skills/envision/project.md +0 -24
- package/skills/explore/SKILL.md +0 -39
- package/skills/formats/reference/architecture.md +0 -84
- package/skills/formats/reference/config.md +0 -80
- package/skills/formats/reference/durable-docs.md +0 -74
- package/skills/formats/reference/specs.md +0 -65
- package/skills/formats/reference/work-items.md +0 -100
- package/skills/formats/templates/adr.md +0 -7
- package/skills/formats/templates/change.md +0 -29
- package/skills/formats/templates/glossary.md +0 -8
- package/skills/formats/templates/roadmap.md +0 -32
- package/skills/formats/templates/spec.md +0 -11
- package/skills/formats/templates/standalone-change.md +0 -46
- package/skills/grilling/SKILL.md +0 -90
- package/skills/grilling/illustrating.md +0 -38
- package/skills/improve/SKILL.md +0 -51
- package/skills/land/SKILL.md +0 -48
- package/skills/next/SKILL.md +0 -31
- package/skills/plan/SKILL.md +0 -62
- package/skills/plan/tasks.md +0 -35
- package/skills/plan/test-plan.md +0 -14
- package/skills/realign/SKILL.md +0 -37
- package/skills/realign/notes/0.0.10.md +0 -131
- package/skills/realign/notes/0.0.11.md +0 -15
- package/skills/realign/notes/0.0.12.md +0 -31
- package/skills/realign/notes/0.0.13.md +0 -11
- package/skills/realign/notes/0.0.2.md +0 -69
- package/skills/realign/notes/0.0.3.md +0 -11
- package/skills/realign/notes/0.0.4.md +0 -27
- package/skills/realign/notes/0.0.5.md +0 -38
- package/skills/realign/notes/0.0.6.md +0 -44
- package/skills/realign/notes/0.0.7.md +0 -11
- package/skills/realign/notes/0.0.8.md +0 -97
- package/skills/realign/notes/0.0.9.md +0 -59
- package/skills/realign/notes.md +0 -40
- package/skills/rethink/SKILL.md +0 -62
- package/skills/rethink/levels.md +0 -54
- package/skills/roadmap/SKILL.md +0 -54
- package/skills/roles/auditor.md +0 -35
- package/skills/roles/design-reviewer.md +0 -43
- package/skills/roles/design-rubric.md +0 -41
- package/skills/roles/plan-rubric.md +0 -11
- package/skills/roles/surveyor.md +0 -41
- package/skills/roles/verifier.md +0 -42
- package/skills/triage/SKILL.md +0 -37
- package/skills/verify/SKILL.md +0 -45
- package/skills/verify/manual-test.md +0 -23
- package/src/adapters/inbound/cli/argument_files.ts +0 -29
- package/src/adapters/inbound/cli/commands/diff.ts +0 -23
- package/src/adapters/inbound/cli/commands/journal.ts +0 -32
- package/src/adapters/inbound/cli/commands/progress.ts +0 -55
- package/src/adapters/inbound/cli/commands/realign.ts +0 -28
- package/src/adapters/inbound/cli/commands/show.ts +0 -96
- package/src/app/use_cases/abandon_work.ts +0 -42
- package/src/app/use_cases/approve_artifact.ts +0 -197
- package/src/app/use_cases/journal_events.ts +0 -64
- package/src/app/use_cases/land_change.ts +0 -134
- package/src/app/use_cases/new_project_doc.ts +0 -48
- package/src/app/use_cases/new_work_item.ts +0 -75
- package/src/app/use_cases/realign_done.ts +0 -45
- package/src/app/use_cases/realign_status.ts +0 -106
- package/src/app/use_cases/record_progress.ts +0 -118
- package/src/app/use_cases/show_elements.ts +0 -18
- package/src/app/use_cases/show_notes.ts +0 -11
- package/src/app/use_cases/show_scope.ts +0 -51
- package/src/app/use_cases/upstream_diff.ts +0 -55
- package/src/domain/approvals/agent_approval.ts +0 -68
- package/src/domain/approvals/evaluate.ts +0 -36
- package/src/domain/approvals/fingerprint.ts +0 -52
- package/src/domain/approvals/rebase.ts +0 -39
- package/src/domain/approvals/records.ts +0 -50
- package/src/domain/approvals/upstream_text.ts +0 -36
- package/src/domain/architecture_sections.ts +0 -23
- package/src/domain/check/decision_comments.ts +0 -38
- package/src/domain/decision_citations.ts +0 -62
- package/src/domain/edits/agents_file.ts +0 -58
- package/src/domain/edits/managed_tables.ts +0 -60
- package/src/domain/edits/progress_block.ts +0 -33
- package/src/domain/edits/status_lines.ts +0 -52
- package/src/domain/elements.ts +0 -126
- package/src/domain/journal.ts +0 -109
- package/src/domain/land/bookkeeping.ts +0 -54
- package/src/domain/land/merge_plan.ts +0 -137
- package/src/domain/land/text_ops.ts +0 -66
- package/src/domain/line_diff.ts +0 -56
- package/src/domain/project/artifact_ref.ts +0 -42
- package/src/domain/project/deltas.ts +0 -49
- package/src/domain/project/element_lookup.ts +0 -124
- package/src/domain/project/living_model.ts +0 -55
- package/src/domain/project/new_items.ts +0 -48
- package/src/domain/project/notes.ts +0 -74
- package/src/domain/project/plan_parts.ts +0 -111
- package/src/domain/project/progress.ts +0 -43
- package/src/domain/project/project_view.ts +0 -72
- package/src/domain/project/roadmap.ts +0 -43
- package/src/domain/project/scope_view.ts +0 -134
- package/src/domain/project/work_items.ts +0 -68
- package/src/domain/status/next_step.ts +0 -181
- package/src/domain/status/pending_rethink.ts +0 -22
- package/src/domain/status/verification.ts +0 -29
- package/src/domain/status/work_status.ts +0 -125
- package/src/domain/validation/decision_rules.ts +0 -76
- package/src/domain/validation/delta_rules.ts +0 -81
- package/src/domain/validation/element_rules.ts +0 -104
- package/src/domain/validation/future_rules.ts +0 -40
- package/src/domain/validation/living_rules.ts +0 -99
- package/src/domain/validation/plan_rules.ts +0 -124
- package/src/domain/validation/project_rules.ts +0 -115
- package/src/domain/validation/size_rules.ts +0 -45
- package/src/domain/validation/vision_rules.ts +0 -41
- package/src/domain/validation/work_item_rules.ts +0 -175
- package/src/domain/versions/realign_notes.ts +0 -72
- package/src/domain/versions/rule_versions.ts +0 -29
- package/src/domain/versions/scheduled_exceptions.ts +0 -41
- package/templates/init/gitattributes +0 -2
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { isUnbuilt } from "../elements.ts";
|
|
2
|
-
import { findIds, kindOf } from "../ids.ts";
|
|
3
|
-
import { elementHash } from "../hashing.ts";
|
|
4
|
-
import { findSection, type MarkdownDoc } from "../markdown.ts";
|
|
5
|
-
import { readDelta, type Delta } from "../project/deltas.ts";
|
|
6
|
-
import { readChangesTable, readScope } from "../project/plan_parts.ts";
|
|
7
|
-
import { readProgress } from "../project/progress.ts";
|
|
8
|
-
import type { ProjectView } from "../project/project_view.ts";
|
|
9
|
-
import { changesOf, featureOf, type WorkItem } from "../project/work_items.ts";
|
|
10
|
-
import { deltaProblems } from "./delta_rules.ts";
|
|
11
|
-
import { malformedIdHeadings, lineOf } from "./living_rules.ts";
|
|
12
|
-
import { changePlanProblems } from "./plan_rules.ts";
|
|
13
|
-
import { error, warning, type Problem } from "./problem.ts";
|
|
14
|
-
import { changeRowSizeProblems, sizeAccepted } from "./size_rules.ts";
|
|
15
|
-
|
|
16
|
-
const REQUIRED_SECTIONS: Record<WorkItem["kind"], string[]> = {
|
|
17
|
-
feature: ["Intent", "Spec delta", "Architecture delta", "Changes"],
|
|
18
|
-
change: ["Scope", "Test plan", "Tasks"],
|
|
19
|
-
standalone: ["Intent", "Spec delta", "Architecture delta", "Scope", "Test plan", "Tasks"],
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/** Rules for every active feature and change. Archived work is history and is not checked. */
|
|
23
|
-
export function workItemProblems(view: ProjectView): Problem[] {
|
|
24
|
-
return view.items.filter((item) => !item.archived).flatMap((item) => itemProblems(view, item));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function itemProblems(view: ProjectView, item: WorkItem): Problem[] {
|
|
28
|
-
const { path, doc } = item;
|
|
29
|
-
const problems: Problem[] = [...malformedIdHeadings(path, doc)];
|
|
30
|
-
const id = doc.frontmatter?.["id"];
|
|
31
|
-
if (id !== item.folderName) problems.push(error(path, 0, "frontmatter", `frontmatter id must be "${item.folderName}", the folder name`));
|
|
32
|
-
const title = doc.frontmatter?.["title"];
|
|
33
|
-
if (typeof title !== "string" || title.trim() === "") problems.push(error(path, 0, "frontmatter", "frontmatter needs a title"));
|
|
34
|
-
for (const section of REQUIRED_SECTIONS[item.kind]) {
|
|
35
|
-
if (findSection(doc, section) === null) problems.push(error(path, undefined, "missing-section", `missing "## ${section}"`));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const feature = item.kind === "change" ? featureOf(view.items, item) : undefined;
|
|
39
|
-
if (item.kind === "change" && feature === undefined) problems.push(error(path, undefined, "orphan-change", "the change's feature.md is missing"));
|
|
40
|
-
const deltaSource = item.kind === "change" ? feature : item;
|
|
41
|
-
const delta = deltaSource === undefined ? readDelta(doc) : readDelta(deltaSource.doc);
|
|
42
|
-
const available = new Set([...view.living.elements.keys(), ...delta.ids]);
|
|
43
|
-
|
|
44
|
-
const text = doc.lines.join("\n");
|
|
45
|
-
for (const cited of findIds(text).filter((cited) => !available.has(cited))) {
|
|
46
|
-
problems.push(error(path, lineOf(text, cited), "unknown-id", `${cited} is cited but defined neither in the living docs nor in this work's deltas`));
|
|
47
|
-
}
|
|
48
|
-
const dropped = droppedScenarios(view, delta);
|
|
49
|
-
for (const cited of findIds(text).filter((cited) => dropped.has(cited))) {
|
|
50
|
-
problems.push(error(path, lineOf(text, cited), "cites-dropped", `${cited} is cited, but ${dropped.get(cited)} in this work's delta leaves it out, so it won't exist once the work lands; describe it in words instead`));
|
|
51
|
-
}
|
|
52
|
-
if (item.kind !== "change") problems.push(...deltaProblems(view, item));
|
|
53
|
-
if (item.kind === "feature") problems.push(...featureScopeProblems(view, item, delta));
|
|
54
|
-
// A landed change is history: its scope named what it built, which is now built.
|
|
55
|
-
const landed = item.kind !== "feature" && readProgress(doc).landed !== undefined;
|
|
56
|
-
if (item.kind !== "feature" && deltaSource !== undefined && !landed) {
|
|
57
|
-
problems.push(...changePlanProblems(view, path, doc, { delta, doc: deltaSource.doc }, available));
|
|
58
|
-
}
|
|
59
|
-
if (item.kind === "standalone") problems.push(...coverageProblems(view, path, delta, doc, [{ name: "this change", ids: scopeIds(item) }]));
|
|
60
|
-
return problems;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** A feature's Changes table covers its deltas, and each change folder agrees with its row. */
|
|
64
|
-
/** More changes than this, and one design review can't hold the whole feature: contradictions slip through to plan. */
|
|
65
|
-
const MAX_CHANGES = 6;
|
|
66
|
-
|
|
67
|
-
function featureScopeProblems(view: ProjectView, feature: WorkItem, delta: Delta): Problem[] {
|
|
68
|
-
const rows = readChangesTable(feature.doc);
|
|
69
|
-
if (rows.length === 0) return [warning(feature.path, undefined, "not-designed", "the feature has no changes yet; architect designs and splits it")];
|
|
70
|
-
const problems: Problem[] = [];
|
|
71
|
-
const landedChanges = new Set(
|
|
72
|
-
[...readProgress(feature.doc).changes].filter(([, state]) => state === "landed").map(([change]) => change),
|
|
73
|
-
);
|
|
74
|
-
const names = rows.map((row) => row.change);
|
|
75
|
-
const extra = rows[MAX_CHANGES];
|
|
76
|
-
if (extra !== undefined && !sizeAccepted(view, feature.ref)) {
|
|
77
|
-
problems.push(warning(feature.path, extra.line, "feature-size", `the feature has ${rows.length} changes, over ${MAX_CHANGES}; split it into features a single design review can hold`));
|
|
78
|
-
}
|
|
79
|
-
problems.push(...changeRowSizeProblems(view, feature, rows, landedChanges));
|
|
80
|
-
for (const row of rows) {
|
|
81
|
-
if (!/^\d\d-[a-z0-9]+(?:-[a-z0-9]+)*$/.test(row.change)) problems.push(error(feature.path, row.line, "change-name", `change "${row.change}" must be named NN-slug`));
|
|
82
|
-
if (names.indexOf(row.change) !== names.lastIndexOf(row.change)) problems.push(error(feature.path, row.line, "change-name", `change ${row.change} is listed twice`));
|
|
83
|
-
for (const dependency of row.dependsOn) {
|
|
84
|
-
const at = names.indexOf(dependency);
|
|
85
|
-
if (at === -1 || at >= names.indexOf(row.change)) problems.push(error(feature.path, row.line, "change-order", `${row.change} depends on ${dependency}, which must be an earlier change`));
|
|
86
|
-
}
|
|
87
|
-
for (const id of landedChanges.has(row.change) ? [] : [...row.delivers, ...row.builds]) {
|
|
88
|
-
const living = view.living.elements.get(id);
|
|
89
|
-
const planned = living !== undefined && isUnbuilt(living.doc, living.element);
|
|
90
|
-
if (!delta.ids.has(id) && !planned) problems.push(error(feature.path, row.line, "scope-not-planned", `${id} must come from this feature's deltas or be planned in the living docs`));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
problems.push(...coverageProblems(view, feature.path, delta, feature.doc, rows.map((row) => ({ name: row.change, ids: [...row.delivers, ...row.builds, ...row.removes], landed: landedChanges.has(row.change) }))));
|
|
94
|
-
|
|
95
|
-
for (const change of changesOf(view.items, feature)) {
|
|
96
|
-
const row = rows.find((r) => r.change === change.folderName);
|
|
97
|
-
if (row === undefined) {
|
|
98
|
-
problems.push(error(change.path, undefined, "change-not-listed", `${change.folderName} is not in the feature's Changes table`));
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
const expected = new Set([...row.delivers, ...row.builds, ...row.removes]);
|
|
102
|
-
const actual = new Set(scopeIds(change));
|
|
103
|
-
const differ = [...expected].filter((id) => !actual.has(id)).concat([...actual].filter((id) => !expected.has(id)));
|
|
104
|
-
if (differ.length > 0) problems.push(error(change.path, readScope(change.doc)?.line, "scope-mismatch", `Scope differs from the feature's Changes row: ${differ.join(", ")}`));
|
|
105
|
-
}
|
|
106
|
-
return problems;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Every delta ID lands exactly once. A requirement is covered by its own ID or one of its
|
|
111
|
-
* scenarios. A scenario must be covered when it is new or its text changed; an unchanged
|
|
112
|
-
* scenario kept by a MODIFIED requirement may be left out.
|
|
113
|
-
*/
|
|
114
|
-
function coverageProblems(
|
|
115
|
-
view: ProjectView,
|
|
116
|
-
path: string,
|
|
117
|
-
delta: Delta,
|
|
118
|
-
deltaDoc: MarkdownDoc,
|
|
119
|
-
groups: Array<{ name: string; ids: string[]; landed?: boolean }>,
|
|
120
|
-
): Problem[] {
|
|
121
|
-
const coveredBy = new Map<string, string[]>();
|
|
122
|
-
const pendingBy = new Map<string, string[]>();
|
|
123
|
-
for (const group of groups) {
|
|
124
|
-
for (const id of group.ids) {
|
|
125
|
-
coveredBy.set(id, [...(coveredBy.get(id) ?? []), group.name]);
|
|
126
|
-
if (group.landed !== true) pendingBy.set(id, [...(pendingBy.get(id) ?? []), group.name]);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
const problems: Problem[] = [];
|
|
130
|
-
// A change that already landed may be followed by one that rebuilds the same element after a rethink.
|
|
131
|
-
const once = (id: string, line: number, required: boolean): void => {
|
|
132
|
-
const pending = pendingBy.get(id) ?? [];
|
|
133
|
-
if (required && (coveredBy.get(id) ?? []).length === 0) problems.push(error(path, line, "uncovered", `${id} is not in any change's scope`));
|
|
134
|
-
if (pending.length > 1) problems.push(error(path, line, "covered-twice", `${id} is in the scope of ${pending.join(" and ")}; it must land exactly once`));
|
|
135
|
-
};
|
|
136
|
-
for (const { element, scenarios } of delta.entries) {
|
|
137
|
-
if (kindOf(element.id) !== "REQ") {
|
|
138
|
-
once(element.id, element.start, true);
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
once(element.id, element.start, false);
|
|
142
|
-
if (![element.id, ...scenarios.map((s) => s.id)].some((id) => coveredBy.has(id))) {
|
|
143
|
-
problems.push(error(path, element.start, "uncovered", `${element.id} is not delivered by any change`));
|
|
144
|
-
}
|
|
145
|
-
for (const scenario of scenarios) {
|
|
146
|
-
const living = view.living.elements.get(scenario.id);
|
|
147
|
-
const changed = living === undefined || elementHash(living.doc, living.element) !== elementHash(deltaDoc, scenario);
|
|
148
|
-
once(scenario.id, scenario.start, element.op !== "REMOVED" && changed);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return problems;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function scopeIds(item: WorkItem): string[] {
|
|
155
|
-
const scope = readScope(item.doc);
|
|
156
|
-
return scope === null ? [] : [...scope.delivers, ...scope.builds, ...scope.removes];
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Living scenarios that a MODIFIED requirement in the delta leaves out, each with that requirement.
|
|
161
|
-
* Land drops them, so a citation of one passes validation now and breaks only at land.
|
|
162
|
-
*/
|
|
163
|
-
function droppedScenarios(view: ProjectView, delta: Delta): Map<string, string> {
|
|
164
|
-
const dropped = new Map<string, string>();
|
|
165
|
-
for (const entry of delta.entries.filter((e) => e.element.op === "MODIFIED" && kindOf(e.element.id) === "REQ")) {
|
|
166
|
-
const living = view.living.elements.get(entry.element.id);
|
|
167
|
-
if (living === undefined) continue;
|
|
168
|
-
const kept = new Set(entry.scenarios.map((s) => s.id));
|
|
169
|
-
for (const scenario of view.living.elements.values()) {
|
|
170
|
-
const nested = scenario.doc === living.doc && scenario.element.start > living.element.start && scenario.element.start < living.element.end;
|
|
171
|
-
if (nested && kindOf(scenario.id) === "SCN" && !kept.has(scenario.id) && !delta.ids.has(scenario.id)) dropped.set(scenario.id, entry.element.id);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return dropped;
|
|
175
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { compareVersions } from "./cruze_version.ts";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A realign note: one change in what Cruze expects of a project, shipped in
|
|
5
|
-
* `skills/realign/notes/<version>.md` so `realign` can bring existing code up to it.
|
|
6
|
-
*/
|
|
7
|
-
export interface RealignNote {
|
|
8
|
-
id: string;
|
|
9
|
-
title: string;
|
|
10
|
-
version: string;
|
|
11
|
-
/** Whether existing code and documents must change, or only new work follows it. */
|
|
12
|
-
appliesTo: "existing" | "new";
|
|
13
|
-
/** Where the rule lives, such as a skill file and its heading. */
|
|
14
|
-
rule: string;
|
|
15
|
-
/** A CLI rule that finds it mechanically, as `<command>:<rule>`. */
|
|
16
|
-
detect?: string;
|
|
17
|
-
/** The question an auditor answers when no CLI rule can. */
|
|
18
|
-
ask?: string;
|
|
19
|
-
migrate?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ParsedNotes {
|
|
23
|
-
notes: RealignNote[];
|
|
24
|
-
problems: string[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const APPLIES: Record<string, RealignNote["appliesTo"]> = { "existing code": "existing", "new work only": "new" };
|
|
28
|
-
|
|
29
|
-
/** Parses one version's notes file. Each note is a `### <id>: <title>` heading with facts under it. */
|
|
30
|
-
export function parseRealignNotes(version: string, text: string): ParsedNotes {
|
|
31
|
-
const notes: RealignNote[] = [];
|
|
32
|
-
const problems: string[] = [];
|
|
33
|
-
const sections = text.split(/^### /m).slice(1);
|
|
34
|
-
for (const section of sections) {
|
|
35
|
-
const [heading = "", ...body] = section.split("\n");
|
|
36
|
-
const match = /^([a-z0-9]+(?:-[a-z0-9]+)*): (.+)$/.exec(heading.trim());
|
|
37
|
-
if (match === null) {
|
|
38
|
-
problems.push(`${version}: "### ${heading.trim()}" must read "### <id>: <title>"`);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
const [, id = "", title = ""] = match;
|
|
42
|
-
const fact = (key: string): string | undefined => body.map((line) => new RegExp(`^- ${key}: (.+)$`).exec(line)?.[1]?.trim()).find((v) => v !== undefined);
|
|
43
|
-
const applies = APPLIES[fact("Applies to") ?? ""];
|
|
44
|
-
const rule = fact("Rule");
|
|
45
|
-
const detect = fact("Detect")?.replace(/`/g, "");
|
|
46
|
-
const ask = fact("Ask");
|
|
47
|
-
const migrate = fact("Migrate");
|
|
48
|
-
if (applies === undefined) problems.push(`${version} ${id}: "- Applies to:" must be "existing code" or "new work only"`);
|
|
49
|
-
if (rule === undefined) problems.push(`${version} ${id}: needs "- Rule:"`);
|
|
50
|
-
if (detect !== undefined && !/^(check|validate):[a-z-]+$/.test(detect)) problems.push(`${version} ${id}: "- Detect:" names a rule as check:<rule> or validate:<rule>`);
|
|
51
|
-
if (applies === "existing" && detect === undefined && ask === undefined) problems.push(`${version} ${id}: a note for existing code needs "- Detect:" or "- Ask:"`);
|
|
52
|
-
if (applies === "existing" && migrate === undefined) problems.push(`${version} ${id}: a note for existing code needs "- Migrate:"`);
|
|
53
|
-
notes.push({
|
|
54
|
-
id,
|
|
55
|
-
title,
|
|
56
|
-
version,
|
|
57
|
-
appliesTo: applies ?? "new",
|
|
58
|
-
rule: rule ?? "",
|
|
59
|
-
...(detect === undefined ? {} : { detect }),
|
|
60
|
-
...(ask === undefined ? {} : { ask }),
|
|
61
|
-
...(migrate === undefined ? {} : { migrate }),
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
const ids = notes.map((n) => n.id);
|
|
65
|
-
ids.filter((id, i) => ids.indexOf(id) !== i).forEach((id) => problems.push(`${version}: note ${id} appears twice`));
|
|
66
|
-
return { notes, problems };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Notes after the version the code meets, up to and including the version installed. */
|
|
70
|
-
export function notesBetween(notes: RealignNote[], standards: string, target: string): RealignNote[] {
|
|
71
|
-
return notes.filter((n) => compareVersions(n.version, standards) > 0 && compareVersions(n.version, target) <= 0);
|
|
72
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { compareVersions } from "./cruze_version.ts";
|
|
2
|
-
|
|
3
|
-
/** The first release of Cruze. A project that records no `standards:` meets its expectations. */
|
|
4
|
-
export const FIRST_RELEASE = "0.0.1";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The release that introduced each rule reporting on existing code, keyed as `<command>:<rule>`.
|
|
8
|
-
* A rule newer than the project's `standards:` still reports, but can't fail CI until the project realigns.
|
|
9
|
-
* Every entry needs a realign note in `skills/realign/notes/<version>.md`; the bundle lint checks it.
|
|
10
|
-
*/
|
|
11
|
-
export const RULE_SINCE: Record<string, string> = {
|
|
12
|
-
"check:test-placement": "0.0.2",
|
|
13
|
-
"validate:outside-module-map": "0.0.2",
|
|
14
|
-
"validate:readme-overview": "0.0.2",
|
|
15
|
-
"validate:feature-size": "0.0.2",
|
|
16
|
-
"validate:change-too-small": "0.0.5",
|
|
17
|
-
"validate:change-too-large": "0.0.5",
|
|
18
|
-
"validate:plan-too-large": "0.0.5",
|
|
19
|
-
"validate:module-path-glob": "0.0.8",
|
|
20
|
-
"validate:decision-citation": "0.0.10",
|
|
21
|
-
"check:decision-citation": "0.0.10",
|
|
22
|
-
"validate:future-unanchored": "0.0.10",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/** Whether a rule arrived after the version the project's code meets, so it waits for `realign`. */
|
|
26
|
-
export function isPending(command: "check" | "validate", rule: string, standards: string): boolean {
|
|
27
|
-
const since = RULE_SINCE[`${command}:${rule}`];
|
|
28
|
-
return since !== undefined && compareVersions(since, standards) > 0;
|
|
29
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { parseDocument, type Document } from "yaml";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* `check.exceptions` entries that `realign` adds for findings a prefactor change will fix,
|
|
5
|
-
* and that landing that change removes. Edited through the YAML document, so comments survive.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface Scheduled {
|
|
9
|
-
path: string;
|
|
10
|
-
note: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const scheduledReason = (version: string, note: string, ref: string): string => `realign ${version} (${note}): scheduled in ${ref}`;
|
|
14
|
-
|
|
15
|
-
/** The config text with an exception for each finding, naming the change that will fix it. */
|
|
16
|
-
export function withScheduledExceptions(configText: string, version: string, ref: string, findings: Scheduled[]): string {
|
|
17
|
-
if (findings.length === 0) return configText;
|
|
18
|
-
const doc = parseDocument(configText);
|
|
19
|
-
const listed = new Set(((doc.getIn(["check", "exceptions"]) as { toJSON(): Array<{ path?: string }> } | undefined)?.toJSON() ?? []).map((e) => e.path));
|
|
20
|
-
for (const finding of findings.filter((f, i) => !listed.has(f.path) && findings.findIndex((o) => o.path === f.path) === i)) {
|
|
21
|
-
doc.addIn(["check", "exceptions"], doc.createNode({ path: finding.path, reason: scheduledReason(version, finding.note, ref) }));
|
|
22
|
-
}
|
|
23
|
-
return render(doc);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** The config text without the exceptions scheduled in a change, once it lands; unchanged when it had none. */
|
|
27
|
-
export function withoutScheduledExceptions(configText: string, ref: string): { text: string; removed: string[] } {
|
|
28
|
-
const doc = parseDocument(configText);
|
|
29
|
-
const exceptions = doc.getIn(["check", "exceptions"]) as { items: Array<{ toJSON(): { path?: string; reason?: string } }> } | undefined;
|
|
30
|
-
if (exceptions === undefined || !Array.isArray(exceptions.items)) return { text: configText, removed: [] };
|
|
31
|
-
const suffix = `: scheduled in ${ref}`;
|
|
32
|
-
const removed = exceptions.items.map((item) => item.toJSON()).filter((e) => e.reason?.endsWith(suffix)).map((e) => e.path ?? "");
|
|
33
|
-
if (removed.length === 0) return { text: configText, removed: [] };
|
|
34
|
-
exceptions.items = exceptions.items.filter((item) => !(item.toJSON().reason ?? "").endsWith(suffix));
|
|
35
|
-
return { text: render(doc), removed };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Renders without wrapping long lines or padding flow lists, so an edit changes only what it touches. */
|
|
39
|
-
function render(doc: Document): string {
|
|
40
|
-
return doc.toString({ lineWidth: 0, flowCollectionPadding: false });
|
|
41
|
-
}
|