@printpractical/cruze 0.0.13 → 0.0.15
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/install.js +1 -0
- 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/install_skills.js +7 -1
- 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 +8 -5
- package/dist/domain/approvals.js +51 -0
- package/dist/domain/check/check_source.js +3 -32
- package/dist/domain/check/imports.js +3 -3
- package/dist/domain/check/rust_imports.js +25 -3
- 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 +65 -0
- package/dist/domain/validation/release_rules.js +45 -0
- package/dist/domain/validation/review_rules.js +21 -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 +22 -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/install.ts +1 -0
- 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/install_skills.ts +7 -1
- 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 +8 -5
- package/src/domain/approvals.ts +51 -0
- package/src/domain/check/check_source.ts +3 -32
- package/src/domain/check/imports.ts +3 -3
- package/src/domain/check/rust_imports.ts +25 -3
- 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 +65 -0
- package/src/domain/validation/release_rules.ts +45 -0
- package/src/domain/validation/review_rules.ts +21 -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,197 +0,0 @@
|
|
|
1
|
-
import { requirePlanReviewClosed, requireUnchangedSinceApproval, } from "../../domain/approvals/agent_approval.js";
|
|
2
|
-
import { evaluateApproval } from "../../domain/approvals/evaluate.js";
|
|
3
|
-
import { fingerprint, isApprovable } from "../../domain/approvals/fingerprint.js";
|
|
4
|
-
import { rebaseNeeded } from "../../domain/approvals/rebase.js";
|
|
5
|
-
import { serializeApprovals, withApproval } from "../../domain/approvals/records.js";
|
|
6
|
-
import { CruzeError } from "../../domain/cruze_error.js";
|
|
7
|
-
import { updateProgress } from "../../domain/edits/progress_block.js";
|
|
8
|
-
import { setStatus, stampMissingStatuses } from "../../domain/edits/status_lines.js";
|
|
9
|
-
import { statusOf } from "../../domain/elements.js";
|
|
10
|
-
import { elementHash } from "../../domain/hashing.js";
|
|
11
|
-
import { parseMarkdown } from "../../domain/markdown.js";
|
|
12
|
-
import { resolveArtifact } from "../../domain/project/artifact_ref.js";
|
|
13
|
-
import { readDelta } from "../../domain/project/deltas.js";
|
|
14
|
-
import { PATHS } from "../../domain/project/layout.js";
|
|
15
|
-
import { isPlanned, readChangesTable } from "../../domain/project/plan_parts.js";
|
|
16
|
-
import { readProgress } from "../../domain/project/progress.js";
|
|
17
|
-
import { approvalFolder, approvalsPath, } from "../../domain/project/project_view.js";
|
|
18
|
-
import { featureOf, } from "../../domain/project/work_items.js";
|
|
19
|
-
|
|
20
|
-
import { SIZE_RULES } from "../../domain/validation/size_rules.js";
|
|
21
|
-
import { validateProject } from "../../domain/validation/validate_project.js";
|
|
22
|
-
import { appendJournal, loadView, withFiles, } from "../project_context.js";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Records a person's approval of a document: its design hash and the hashes of everything
|
|
53
|
-
* upstream it relies on. Refuses invalid documents and documents whose upstream isn't approved.
|
|
54
|
-
*/
|
|
55
|
-
export async function approveArtifact(deps , ref , options = {}) {
|
|
56
|
-
let view = await loadView(deps.files);
|
|
57
|
-
const { path, item } = resolveArtifact(view, ref);
|
|
58
|
-
if (!isApprovable(view, path)) throw new CruzeError("not-approvable", `${path} is not something you approve; it changes through approved work`);
|
|
59
|
-
requireUpstreamApproved(view, path);
|
|
60
|
-
if (item?.kind === "feature" && readChangesTable(item.doc).length === 0) {
|
|
61
|
-
throw new CruzeError("not-designed", `${path} has no changes yet; architect designs and splits it before approval`);
|
|
62
|
-
}
|
|
63
|
-
if (item !== undefined && item.kind !== "feature" && !isPlanned(item.doc)) {
|
|
64
|
-
throw new CruzeError("not-planned", `${path} has no test plan or tasks yet; plan it before approving`);
|
|
65
|
-
}
|
|
66
|
-
const before = evaluateApproval(view, path);
|
|
67
|
-
if (options.agent === "plan-review") requirePlanReviewClosed(view.journal, item ?? notWork(path));
|
|
68
|
-
if (options.agent === "rethink") requireUnchangedSinceApproval(view.journal, item, path, before);
|
|
69
|
-
|
|
70
|
-
const edits = new Map ();
|
|
71
|
-
let text = view.snapshot.get(path) ?? "";
|
|
72
|
-
let stamped = [];
|
|
73
|
-
if (path === PATHS.architecture) ({ text, stamped } = stampMissingStatuses(text));
|
|
74
|
-
const replanned = options.replan ?? [];
|
|
75
|
-
if (replanned.length > 0 && path !== PATHS.architecture) throw new CruzeError("replan-architecture-only", "--replan applies to the architecture");
|
|
76
|
-
for (const id of replanned) {
|
|
77
|
-
const element = view.living.elements.get(id);
|
|
78
|
-
if (element?.path !== PATHS.architecture || statusOf(element.doc, element.element) !== "built") {
|
|
79
|
-
throw new CruzeError("not-built", `${id} is not a built element of ${PATHS.architecture}`);
|
|
80
|
-
}
|
|
81
|
-
text = setStatus(text, id, "planned");
|
|
82
|
-
}
|
|
83
|
-
let bound ;
|
|
84
|
-
if (item !== undefined && item.kind !== "feature" && readProgress(parseMarkdown(text)).branch === undefined) {
|
|
85
|
-
bound = (await deps.repository.currentBranch()) ?? undefined;
|
|
86
|
-
text = updateProgress(text, (progress) => {
|
|
87
|
-
if (bound !== undefined) progress.branch = bound;
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
if (text !== view.snapshot.get(path)) edits.set(path, text);
|
|
91
|
-
view = withFiles(view, edits);
|
|
92
|
-
|
|
93
|
-
const problems = validateProject(view).filter((p) => p.path === path);
|
|
94
|
-
const errors = problems.filter((p) => p.severity === "error");
|
|
95
|
-
if (errors.length > 0) {
|
|
96
|
-
const listing = errors.map((p) => ` ${p.path}:${p.line ?? ""} ${p.message}`).join("\n");
|
|
97
|
-
throw new CruzeError("invalid", `${path} has ${errors.length} problem(s) to fix before approval:\n${listing}`);
|
|
98
|
-
}
|
|
99
|
-
requireSizesAccepted(problems, path);
|
|
100
|
-
if (item !== undefined) requireNoDecisionCitations(problems, path);
|
|
101
|
-
|
|
102
|
-
const { hash, upstream } = fingerprint(view, path, parseMarkdown(text));
|
|
103
|
-
const folder = approvalFolder(view, path);
|
|
104
|
-
const current = view.approvals.get(folder) ?? { version: 1, approvals: [], merged: {} };
|
|
105
|
-
const rebased = options.rebase ?? [];
|
|
106
|
-
const deltaIds = item === undefined || item.kind === "change" ? new Set () : readDelta(item.doc).ids;
|
|
107
|
-
if (item !== undefined) requireRebased(view, item, rebased);
|
|
108
|
-
const merged = { ...current.merged };
|
|
109
|
-
for (const id of rebased) {
|
|
110
|
-
const living = view.living.elements.get(id);
|
|
111
|
-
if (living === undefined || (merged[id] === undefined && !deltaIds.has(id))) {
|
|
112
|
-
throw new CruzeError("not-in-delta", `${id} is not a living element that ${item?.ref ?? path} changes or merged`);
|
|
113
|
-
}
|
|
114
|
-
if (merged[id] !== undefined) merged[id] = elementHash(living.doc, living.element);
|
|
115
|
-
}
|
|
116
|
-
const approvals = withApproval({ ...current, merged }, {
|
|
117
|
-
artifact: path,
|
|
118
|
-
hash,
|
|
119
|
-
upstream,
|
|
120
|
-
approvedAt: deps.clock.now().toISOString(),
|
|
121
|
-
by: (await deps.repository.userName()) ?? "unknown",
|
|
122
|
-
...(options.agent === undefined ? {} : { basis: options.agent }),
|
|
123
|
-
});
|
|
124
|
-
for (const [changed, content] of edits) await deps.files.writeText(changed, content);
|
|
125
|
-
await deps.files.writeText(approvalsPath(folder), serializeApprovals(approvals));
|
|
126
|
-
const restamped = options.agent === "rethink" ? before.changed : undefined;
|
|
127
|
-
await appendJournal(deps, folder, "approve", {
|
|
128
|
-
artifact: path,
|
|
129
|
-
hash,
|
|
130
|
-
...(replanned.length > 0 ? { replanned } : {}),
|
|
131
|
-
...(rebased.length > 0 ? { rebased } : {}),
|
|
132
|
-
...(options.agent === undefined ? {} : { basis: options.agent }),
|
|
133
|
-
...(restamped === undefined ? {} : { restamped }),
|
|
134
|
-
});
|
|
135
|
-
const report = { artifact: path, hash, upstream: Object.keys(upstream), stamped, replanned, rebased };
|
|
136
|
-
if (bound !== undefined) report.bound = bound;
|
|
137
|
-
if (options.agent !== undefined) report.basis = options.agent;
|
|
138
|
-
if (restamped !== undefined) report.restamped = restamped;
|
|
139
|
-
return report;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* A feature or standalone change whose delta changes an element must have been written against
|
|
144
|
-
* that element's current text. When the living element changed since the last approval, through
|
|
145
|
-
* a rethink or another land, the delta would overwrite that edit, so approval waits until the
|
|
146
|
-
* delta is reconciled and the element is named with --rebase.
|
|
147
|
-
*/
|
|
148
|
-
function requireRebased(view , item , rebased ) {
|
|
149
|
-
const moved = rebaseNeeded(view, item).filter((id) => !rebased.includes(id));
|
|
150
|
-
if (moved.length > 0) {
|
|
151
|
-
throw new CruzeError(
|
|
152
|
-
"rebase-required",
|
|
153
|
-
`the living text of ${moved.join(", ")} changed since the last approval, and this work's delta changes ${moved.length === 1 ? "it" : "them"}. Rewrite the delta to keep that change, then approve with ${moved.map((id) => `--rebase ${id}`).join(" ")}`,
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* A size warning stands until the work is split or the user accepts it under `changes.exceptions`.
|
|
160
|
-
* A 28-task change approved over its warning went on to 26 deviations, so approval waits for one or the other.
|
|
161
|
-
*/
|
|
162
|
-
function requireSizesAccepted(problems , path ) {
|
|
163
|
-
const standing = problems.filter((p) => SIZE_RULES.includes(p.rule));
|
|
164
|
-
if (standing.length === 0) return;
|
|
165
|
-
const listing = standing.map((p) => ` ${p.message} [${p.rule}]`).join("\n");
|
|
166
|
-
throw new CruzeError(
|
|
167
|
-
"size-unaccepted",
|
|
168
|
-
`${path} has ${standing.length} size warning(s). Split the work through rethink, or record the user's acceptance under changes.exceptions in .cruze/config.yaml, as the item's ref and their reason:\n${listing}`,
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* A delta that cites decisions by number would merge them into the living docs, where they dangle once
|
|
174
|
-
* the work is archived. Existing docs only warn, so approval of new or redesigned work is where it stops.
|
|
175
|
-
*/
|
|
176
|
-
function requireNoDecisionCitations(problems , path ) {
|
|
177
|
-
const standing = problems.filter((p) => p.rule === "decision-citation");
|
|
178
|
-
if (standing.length === 0) return;
|
|
179
|
-
const listing = standing.map((p) => ` ${p.path}:${p.line ?? ""} ${p.message}`).join("\n");
|
|
180
|
-
throw new CruzeError("decision-cited", `${path} cites decisions by number in its delta, which merges into the living docs:\n${listing}`);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function notWork(path ) {
|
|
184
|
-
throw new CruzeError("needs-user", `${path} is a project document, so the user approves it`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** A change needs its feature approved; a feature or standalone change needs the architecture approved. */
|
|
188
|
-
function requireUpstreamApproved(view , path ) {
|
|
189
|
-
const item = view.items.find((i) => i.path === path);
|
|
190
|
-
if (item === undefined) return;
|
|
191
|
-
const upstream = item.kind === "change" ? featureOf(view.items, item)?.path : view.snapshot.has(PATHS.architecture) ? PATHS.architecture : undefined;
|
|
192
|
-
if (upstream === undefined) return;
|
|
193
|
-
const status = evaluateApproval(view, upstream);
|
|
194
|
-
if (status.state !== "approved") {
|
|
195
|
-
throw new CruzeError("upstream-not-approved", `approve ${upstream} first; it is ${status.state}${status.changed.length > 0 ? ` (${status.changed.join(", ")})` : ""}`);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { CruzeError } from "../../domain/cruze_error.js";
|
|
2
|
-
import { knownPitfalls } from "../../domain/edits/agents_file.js";
|
|
3
|
-
import { journalPath, parseJournal, recordedEventProblems, recordedFields, } from "../../domain/journal.js";
|
|
4
|
-
import { resolveArtifact } from "../../domain/project/artifact_ref.js";
|
|
5
|
-
import { appendJournal, loadView, } from "../project_context.js";
|
|
6
|
-
|
|
7
|
-
/** Records an event a skill observed: a rethink, a review round, a disposition, an override or a bug. */
|
|
8
|
-
export async function recordEvent(deps , event , fields , item ) {
|
|
9
|
-
const problems = recordedEventProblems(event, fields);
|
|
10
|
-
if (problems.length > 0) throw new CruzeError("invalid-event", problems.join("; "));
|
|
11
|
-
let folder = ".cruze";
|
|
12
|
-
let recorded = recordedFields(fields);
|
|
13
|
-
if (item !== undefined) {
|
|
14
|
-
const resolved = resolveArtifact(await loadView(deps.files), item).item;
|
|
15
|
-
if (resolved === undefined) throw new CruzeError("not-found", `"${item}" is not a feature or change`);
|
|
16
|
-
folder = resolved.folder;
|
|
17
|
-
recorded = { ...recorded, item: resolved.ref };
|
|
18
|
-
}
|
|
19
|
-
return appendJournal(deps, folder, event, { ...recorded, cruze: deps.bundle.version });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* The journal of the work in flight and the project, or of one feature or change (a feature's changes' too).
|
|
24
|
-
* Landed work's entries are history, listed only with `all`, since every entry ever recorded is too much to read.
|
|
25
|
-
*/
|
|
26
|
-
export async function listEvents(deps , event , item , all = false) {
|
|
27
|
-
const view = await loadView(deps.files);
|
|
28
|
-
let folders = [".cruze", ...view.items.filter((i) => all || !i.archived).map((i) => i.folder)];
|
|
29
|
-
if (item !== undefined) {
|
|
30
|
-
const resolved = resolveArtifact(view, item).item;
|
|
31
|
-
if (resolved === undefined) throw new CruzeError("not-found", `"${item}" is not a feature or change`);
|
|
32
|
-
folders = [resolved, ...view.items.filter((i) => resolved.kind === "feature" && i.featureRef === resolved.ref)].map((part) => part.folder);
|
|
33
|
-
}
|
|
34
|
-
const entries = folders.flatMap((folder) => parseJournal(view.snapshot.get(journalPath(folder))).entries).sort((a, b) => a.at.localeCompare(b.at));
|
|
35
|
-
return event === undefined ? entries : entries.filter((entry) => entry.event === event);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Bundles the journal, and the project's known pitfalls, for the framework feedback loop. By default the project name and
|
|
50
|
-
* the approver's identity are redacted, since the bundle may leave the project.
|
|
51
|
-
*/
|
|
52
|
-
export async function exportFeedback(deps , redact ) {
|
|
53
|
-
const view = await loadView(deps.files);
|
|
54
|
-
const project = view.config?.config?.project ?? "";
|
|
55
|
-
const scrub = (value ) => {
|
|
56
|
-
if (typeof value === "string") return project === "" ? value : value.split(project).join("<project>");
|
|
57
|
-
if (Array.isArray(value)) return value.map(scrub);
|
|
58
|
-
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, scrub(v)]));
|
|
59
|
-
return value;
|
|
60
|
-
};
|
|
61
|
-
const entries = redact ? view.journal.map((entry) => ({ ...(scrub(entry) ), by: "<redacted>" })) : view.journal;
|
|
62
|
-
const pitfalls = knownPitfalls((await deps.files.readText("AGENTS.md")) ?? "").map((line) => (redact ? (scrub(line) ) : line));
|
|
63
|
-
return { format: "cruze-feedback/1", cruze: deps.bundle.version, exported_at: deps.clock.now().toISOString(), redacted: redact, entries, pitfalls };
|
|
64
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { evaluateApproval } from "../../domain/approvals/evaluate.js";
|
|
2
|
-
import { fingerprint } from "../../domain/approvals/fingerprint.js";
|
|
3
|
-
import { serializeApprovals, withApproval, } from "../../domain/approvals/records.js";
|
|
4
|
-
import { CruzeError } from "../../domain/cruze_error.js";
|
|
5
|
-
import { renderAgentsFile } from "../../domain/edits/agents_file.js";
|
|
6
|
-
import { applyBookkeeping } from "../../domain/land/bookkeeping.js";
|
|
7
|
-
import { planMerge } from "../../domain/land/merge_plan.js";
|
|
8
|
-
import { parseMarkdown } from "../../domain/markdown.js";
|
|
9
|
-
import { resolveChange } from "../../domain/project/artifact_ref.js";
|
|
10
|
-
import { PATHS } from "../../domain/project/layout.js";
|
|
11
|
-
import { readTasks } from "../../domain/project/plan_parts.js";
|
|
12
|
-
import { readProgress } from "../../domain/project/progress.js";
|
|
13
|
-
import { approvalFolder, approvalsPath, } from "../../domain/project/project_view.js";
|
|
14
|
-
import { changesOf, featureOf, } from "../../domain/project/work_items.js";
|
|
15
|
-
import { isVerified } from "../../domain/status/verification.js";
|
|
16
|
-
import { activeChange } from "../../domain/status/work_status.js";
|
|
17
|
-
import { validateProject } from "../../domain/validation/validate_project.js";
|
|
18
|
-
import { withoutScheduledExceptions } from "../../domain/versions/scheduled_exceptions.js";
|
|
19
|
-
import { appendJournal, loadView, withFiles, } from "../project_context.js";
|
|
20
|
-
|
|
21
|
-
const AGENTS_FILE = "AGENTS.md";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Lands a finished change: merges its work into the living docs, re-stamps the approvals the
|
|
37
|
-
* merge would otherwise make stale, records the land, and archives the work once it is finished.
|
|
38
|
-
* Nothing is written unless the merged docs still validate.
|
|
39
|
-
*/
|
|
40
|
-
export async function landChange(deps , ref , options = {}) {
|
|
41
|
-
const view = await loadView(deps.files);
|
|
42
|
-
const change = ref !== undefined ? resolveChange(view, ref) : activeChange(view, await deps.repository.currentBranch());
|
|
43
|
-
if (change === undefined) throw new CruzeError("no-active-change", "no change is bound to this branch; name one to land");
|
|
44
|
-
const source = change.kind === "change" ? featureOf(view.items, change) : change;
|
|
45
|
-
if (source === undefined) throw new CruzeError("orphan-change", `${change.ref} has no feature.md`);
|
|
46
|
-
requireReady(view, change, source);
|
|
47
|
-
const verified = isVerified(view.journal, change);
|
|
48
|
-
if (!verified && (options.override ?? "").trim() === "") {
|
|
49
|
-
throw new CruzeError("not-verified", `${change.ref} has no accepted verification; run verify, or land with --override <reason>`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const restamp = [PATHS.architecture, source.path, ...(source === change ? [] : changesOf(view.items, source).map((c) => c.path))]
|
|
53
|
-
.filter((path, i, all) => all.indexOf(path) === i && view.snapshot.has(path) && evaluateApproval(view, path).state === "approved");
|
|
54
|
-
|
|
55
|
-
const plan = planMerge(view, change, source);
|
|
56
|
-
const commit = await deps.repository.headCommit();
|
|
57
|
-
const landed = `${deps.clock.today()}${commit === null ? "" : ` (${commit})`}`;
|
|
58
|
-
const books = applyBookkeeping(view, change, source, landed, plan.files);
|
|
59
|
-
const merged = withFiles(view, plan.files);
|
|
60
|
-
const recorded = { ...(merged.approvals.get(source.folder) ?? { version: 1, approvals: [], merged: {} }), merged: plan.merged };
|
|
61
|
-
const checked = withFiles(merged, new Map([[approvalsPath(source.folder), serializeApprovals(recorded)]]));
|
|
62
|
-
requireStillValid(view, checked, [source.path, ...changesOf(view.items, source).map((c) => c.path)]);
|
|
63
|
-
|
|
64
|
-
const approvals = new Map ();
|
|
65
|
-
const by = (await deps.repository.userName()) ?? "unknown";
|
|
66
|
-
for (const path of restamp) {
|
|
67
|
-
const folder = approvalFolder(merged, path);
|
|
68
|
-
const { hash, upstream } = fingerprint(merged, path, parseMarkdown(merged.snapshot.get(path) ?? ""));
|
|
69
|
-
const file = approvals.get(folder) ?? merged.approvals.get(folder) ?? { version: 1, approvals: [], merged: {} };
|
|
70
|
-
approvals.set(folder, withApproval(file, { artifact: path, hash, upstream, approvedAt: deps.clock.now().toISOString(), by, basis: `land ${change.ref}` }));
|
|
71
|
-
}
|
|
72
|
-
const sourceApprovals = approvals.get(source.folder) ?? merged.approvals.get(source.folder) ?? { version: 1, approvals: [], merged: {} };
|
|
73
|
-
approvals.set(source.folder, { ...sourceApprovals, merged: plan.merged });
|
|
74
|
-
|
|
75
|
-
for (const [path, text] of plan.files) await deps.files.writeText(path, text);
|
|
76
|
-
for (const [folder, file] of approvals) await deps.files.writeText(approvalsPath(folder), serializeApprovals(file));
|
|
77
|
-
for (const path of restamp) {
|
|
78
|
-
const record = approvals.get(approvalFolder(merged, path))?.approvals.find((r) => r.artifact === path);
|
|
79
|
-
await appendJournal(deps, approvalFolder(merged, path), "approve", { artifact: path, hash: record?.hash, basis: `land ${change.ref}` });
|
|
80
|
-
}
|
|
81
|
-
if (!verified) await appendJournal(deps, source.folder, "override", { gate: "land", reason: options.override, item: change.ref });
|
|
82
|
-
await appendJournal(deps, source.folder, "land", { change: change.ref, merged: plan.mergedIds, built: plan.built, removed: plan.removed, release: books.release, commit });
|
|
83
|
-
if (books.archive !== undefined) await deps.files.move(books.archive.from, books.archive.to);
|
|
84
|
-
const agents = await deps.files.readText(AGENTS_FILE);
|
|
85
|
-
if (agents !== undefined) {
|
|
86
|
-
const rendered = renderAgentsFile(agents, merged);
|
|
87
|
-
if (rendered !== agents) await deps.files.writeText(AGENTS_FILE, rendered);
|
|
88
|
-
}
|
|
89
|
-
const unexcepted = await clearScheduledExceptions(deps, [change.ref, source.ref]);
|
|
90
|
-
|
|
91
|
-
const report = { change: change.ref, merged: plan.mergedIds, built: plan.built, removed: plan.removed, restamped: restamp, finished: books.finished, unexcepted };
|
|
92
|
-
if (books.archive !== undefined) report.archivedTo = books.archive.to;
|
|
93
|
-
return report;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** The change, its feature and the architecture are approved and current, valid, and every task is done. */
|
|
97
|
-
function requireReady(view , change , source ) {
|
|
98
|
-
const reasons = [];
|
|
99
|
-
for (const path of [change.path, source.path, PATHS.architecture].filter((p, i, all) => all.indexOf(p) === i && view.snapshot.has(p))) {
|
|
100
|
-
const status = evaluateApproval(view, path);
|
|
101
|
-
if (status.state !== "approved") reasons.push(`${path} is ${status.state}${status.changed.length > 0 ? ` (${status.changed.join(", ")})` : ""}`);
|
|
102
|
-
}
|
|
103
|
-
const errors = validateProject(view).filter((p) => p.severity === "error" && (p.path === change.path || p.path === source.path));
|
|
104
|
-
reasons.push(...errors.map((p) => `${p.path}:${p.line ?? ""} ${p.message}`));
|
|
105
|
-
const progress = readProgress(change.doc);
|
|
106
|
-
const open = readTasks(change.doc).tasks.filter((task) => (progress.tasks.get(task.number) ?? null) === null).map((task) => task.number);
|
|
107
|
-
if (open.length > 0) reasons.push(`tasks not done: ${open.join(", ")}`);
|
|
108
|
-
if (reasons.length > 0) throw new CruzeError("not-ready", `${change.ref} cannot land yet:\n ${reasons.join("\n ")}`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** The merge may not introduce errors into the living docs, or into the work it lands, such as a citation of an ID it removes. */
|
|
112
|
-
function requireStillValid(before , after , work ) {
|
|
113
|
-
const key = (p ) => `${p.path}|${p.message}`;
|
|
114
|
-
const existing = new Set(validateProject(before).map(key));
|
|
115
|
-
const watched = (path ) => path.startsWith("docs/") || work.includes(path);
|
|
116
|
-
const introduced = validateProject(after).filter((p) => p.severity === "error" && watched(p.path) && !existing.has(key(p)));
|
|
117
|
-
if (introduced.length > 0) {
|
|
118
|
-
throw new CruzeError("merge-invalid", `landing would leave errors behind; nothing was written:\n ${introduced.map((p) => `${p.path}:${p.line ?? ""} ${p.message}`).join("\n ")}`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** Removes the exceptions `realign` scheduled in the landed work, so CI checks those files again. */
|
|
123
|
-
async function clearScheduledExceptions(deps , refs ) {
|
|
124
|
-
let text = await deps.files.readText(PATHS.config);
|
|
125
|
-
if (text === undefined) return [];
|
|
126
|
-
const removed = [];
|
|
127
|
-
for (const ref of [...new Set(refs)]) {
|
|
128
|
-
const result = withoutScheduledExceptions(text, ref);
|
|
129
|
-
text = result.text;
|
|
130
|
-
removed.push(...result.removed);
|
|
131
|
-
}
|
|
132
|
-
if (removed.length > 0) await deps.files.writeText(PATHS.config, text);
|
|
133
|
-
return removed;
|
|
134
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { CruzeError } from "../../domain/cruze_error.js";
|
|
2
|
-
import { PATHS } from "../../domain/project/layout.js";
|
|
3
|
-
import { NOTE_KINDS } from "../../domain/project/notes.js";
|
|
4
|
-
import { requireSlug } from "../../domain/project/new_items.js";
|
|
5
|
-
import { renderTemplate } from "../../domain/scaffold.js";
|
|
6
|
-
import { appendJournal, loadView, } from "../project_context.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export const PROJECT_DOC_KINDS = ["vision", "glossary", "architecture", "roadmap"];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/** Creates one of the project's living documents from its template, named for the project. */
|
|
18
|
-
export async function createProjectDoc(deps , kind ) {
|
|
19
|
-
const view = await loadView(deps.files);
|
|
20
|
-
const path = PATHS[kind];
|
|
21
|
-
if (view.snapshot.has(path)) throw new CruzeError("exists", `${path} already exists`);
|
|
22
|
-
const project = view.config?.config?.project ?? "";
|
|
23
|
-
if (project === "") throw new CruzeError("no-config", `${PATHS.config} is missing or has no project name; run cruze init first`);
|
|
24
|
-
const values = { project_name: project, id: "", title: "", date: deps.clock.today() };
|
|
25
|
-
await deps.files.writeText(path, renderTemplate(await deps.bundle.formatTemplate(`${kind}.md`), values));
|
|
26
|
-
await appendJournal(deps, ".cruze", "new", { kind, ref: path });
|
|
27
|
-
return { kind, path };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Creates a dated note in `.cruze/notes/`, such as a captured explore session or research evidence. */
|
|
31
|
-
/**
|
|
32
|
-
* A dated note, saying what kind it is and what it is about, so `cruze show` finds it for the work it
|
|
33
|
-
* informs: `project`, feature slugs, or context and capability names.
|
|
34
|
-
*/
|
|
35
|
-
export async function createNote(deps , slug , title , kind , about ) {
|
|
36
|
-
requireSlug(slug);
|
|
37
|
-
if (title.trim() === "") throw new CruzeError("missing-title", "a title is required");
|
|
38
|
-
if (!(NOTE_KINDS ).includes(kind)) throw new CruzeError("note-kind", `a note's kind is one of ${NOTE_KINDS.join(", ")}`);
|
|
39
|
-
const subjects = about.split(",").map((a) => a.trim()).filter((a) => a !== "");
|
|
40
|
-
if (subjects.length === 0) throw new CruzeError("note-about", "name what the note is about: project, feature slugs, or contexts, comma-separated");
|
|
41
|
-
const view = await loadView(deps.files);
|
|
42
|
-
const date = deps.clock.today();
|
|
43
|
-
const base = `${PATHS.notesDir}/${date}-${slug}`;
|
|
44
|
-
let path = `${base}.md`;
|
|
45
|
-
for (let n = 2; view.snapshot.has(path); n++) path = `${base}-${n}.md`;
|
|
46
|
-
await deps.files.writeText(path, `# ${title}\n\n- Date: ${date}\n- Kind: ${kind}\n- About: ${subjects.join(", ")}\n\n`);
|
|
47
|
-
return { kind: "note", path };
|
|
48
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { CruzeError } from "../../domain/cruze_error.js";
|
|
2
|
-
import { upsertRow } from "../../domain/edits/managed_tables.js";
|
|
3
|
-
import { resolveArtifact } from "../../domain/project/artifact_ref.js";
|
|
4
|
-
import { PATHS } from "../../domain/project/layout.js";
|
|
5
|
-
import { allocateId, changeRow, fillTemplate, requireSlug } from "../../domain/project/new_items.js";
|
|
6
|
-
import { renderTemplate } from "../../domain/scaffold.js";
|
|
7
|
-
import { appendJournal, loadView, } from "../project_context.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/** Creates a feature, change or ADR from its template, with a fresh ID. */
|
|
28
|
-
export async function createWorkItem(deps , request ) {
|
|
29
|
-
const view = await loadView(deps.files);
|
|
30
|
-
const date = deps.clock.today();
|
|
31
|
-
const values = { id: "", title: request.title, title_quoted: JSON.stringify(request.title), date, project_name: "" };
|
|
32
|
-
if (request.title.trim() === "") throw new CruzeError("missing-title", "a title is required");
|
|
33
|
-
|
|
34
|
-
if (request.kind === "adr") {
|
|
35
|
-
requireSlug(request.slug);
|
|
36
|
-
const path = `${PATHS.adrDir}/${date}-${request.slug}.md`;
|
|
37
|
-
if (view.snapshot.has(path)) throw new CruzeError("exists", `${path} already exists`);
|
|
38
|
-
const text = renderTemplate(await deps.bundle.formatTemplate("adr.md"), values).replace(/^# .*$/m, `# ${request.title}`);
|
|
39
|
-
await deps.files.writeText(path, text);
|
|
40
|
-
await appendJournal(deps, ".cruze", "new", { kind: "adr", ref: path });
|
|
41
|
-
return { kind: "adr", ref: `ADR-${date}-${request.slug}`, path };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (request.kind === "change" && request.feature !== undefined) {
|
|
45
|
-
const { item: feature } = resolveArtifact(view, request.feature);
|
|
46
|
-
if (feature === undefined || feature.kind !== "feature" || feature.archived) throw new CruzeError("not-a-feature", `"${request.feature}" is not an active feature`);
|
|
47
|
-
const row = changeRow(view, feature, request.slug);
|
|
48
|
-
const folder = `${feature.folder}/changes/${row.change}`;
|
|
49
|
-
const template = renderTemplate(await deps.bundle.formatTemplate("change.md"), { ...values, id: row.change });
|
|
50
|
-
await deps.files.writeText(`${folder}/change.md`, fillTemplate(template, { delivers: row.delivers, builds: [...row.builds, ...row.removes] }));
|
|
51
|
-
await appendJournal(deps, feature.folder, "new", { kind: "change", ref: `${feature.ref}/${row.change}` });
|
|
52
|
-
return { kind: "change", ref: `${feature.ref}/${row.change}`, path: `${folder}/change.md` };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const kind = request.kind === "feature" ? "feature" : "standalone";
|
|
56
|
-
const id = allocateId(view, date, request.slug);
|
|
57
|
-
const folder = `${kind === "feature" ? PATHS.featuresDir : PATHS.changesDir}/${id}`;
|
|
58
|
-
const roadmap = request.roadmap ?? (roadmapHasItem(view.snapshot.get(PATHS.roadmap), request.slug) ? request.slug : undefined);
|
|
59
|
-
const template = renderTemplate(await deps.bundle.formatTemplate(kind === "feature" ? "feature.md" : "standalone-change.md"), { ...values, id });
|
|
60
|
-
const path = `${folder}/${kind === "feature" ? "feature.md" : "change.md"}`;
|
|
61
|
-
await deps.files.writeText(path, fillTemplate(template, roadmap === undefined ? {} : { roadmap }));
|
|
62
|
-
const roadmapText = view.snapshot.get(PATHS.roadmap);
|
|
63
|
-
if (roadmap !== undefined && roadmapText !== undefined) {
|
|
64
|
-
await deps.files.writeText(PATHS.roadmap, upsertRow(roadmapText, "Status", [roadmap, id, "designing"]));
|
|
65
|
-
}
|
|
66
|
-
await appendJournal(deps, folder, "new", { kind, ref: id });
|
|
67
|
-
return { kind, ref: id, path };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Whether a roadmap phase lists this slug as an item. */
|
|
71
|
-
function roadmapHasItem(roadmap , slug ) {
|
|
72
|
-
if (roadmap === undefined) return false;
|
|
73
|
-
const phases = roadmap.split(/^## /m).find((section) => section.startsWith("Phases")) ?? "";
|
|
74
|
-
return phases.split("\n").some((line) => line.startsWith(`| ${slug} |`));
|
|
75
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { CruzeError } from "../../domain/cruze_error.js";
|
|
2
|
-
import { resolveArtifact } from "../../domain/project/artifact_ref.js";
|
|
3
|
-
import { PATHS } from "../../domain/project/layout.js";
|
|
4
|
-
import { withVersionLine } from "../../domain/versions/cruze_version.js";
|
|
5
|
-
import { withScheduledExceptions } from "../../domain/versions/scheduled_exceptions.js";
|
|
6
|
-
import { appendJournal, loadView, } from "../project_context.js";
|
|
7
|
-
import { realignStatus } from "./realign_status.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Moves `standards:` up to the installed Cruze once every mechanical finding is fixed, waived,
|
|
20
|
-
* or scheduled in a change. A scheduled `check` finding becomes an exception naming that change,
|
|
21
|
-
* so CI stays green until it lands, and landing it removes the exception.
|
|
22
|
-
*/
|
|
23
|
-
export async function realignDone(deps , options ) {
|
|
24
|
-
const status = await realignStatus(deps, { full: false });
|
|
25
|
-
if (status.standards === status.target) return { from: status.standards, to: status.target, excepted: [] };
|
|
26
|
-
const open = status.notes.flatMap((note) => note.findings.filter((f) => !f.waived).map((f) => ({ note, finding: f })));
|
|
27
|
-
let scheduled ;
|
|
28
|
-
if (open.length > 0) {
|
|
29
|
-
if (options.change === undefined) {
|
|
30
|
-
const list = open.map(({ note, finding }) => `${note.id}: ${finding.path}${finding.line === undefined ? "" : `:${finding.line}`}`);
|
|
31
|
-
throw new CruzeError("realign-open", `fix, waive or schedule these first, then pass --change <ref> for the change that fixes the rest:\n ${list.join("\n ")}`);
|
|
32
|
-
}
|
|
33
|
-
const item = resolveArtifact(await loadView(deps.files), options.change).item;
|
|
34
|
-
if (item === undefined) throw new CruzeError("not-found", `"${options.change}" is not a feature or change; create the prefactor change first`);
|
|
35
|
-
scheduled = item.ref;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const excepted = open.filter(({ note }) => note.detect?.startsWith("check:") === true).map(({ note, finding }) => ({ path: finding.path, note: note.id }));
|
|
39
|
-
const config = (await deps.files.readText(PATHS.config)) ?? "";
|
|
40
|
-
const withExceptions = scheduled === undefined ? config : withScheduledExceptions(config, status.target, scheduled, excepted);
|
|
41
|
-
await deps.files.writeText(PATHS.config, withVersionLine(withExceptions, "standards", status.target));
|
|
42
|
-
const paths = [...new Set(excepted.map((e) => e.path))];
|
|
43
|
-
await appendJournal(deps, ".cruze", "realign", { from: status.standards, to: status.target, ...(scheduled === undefined ? {} : { scheduled }), excepted: paths });
|
|
44
|
-
return { from: status.standards, to: status.target, ...(scheduled === undefined ? {} : { scheduled }), excepted: paths };
|
|
45
|
-
}
|