@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,14 +1,9 @@
|
|
|
1
1
|
|
|
2
|
-
import { runCheck,
|
|
3
|
-
import { runDiff } from "./commands/diff.js";
|
|
2
|
+
import { runCheck, runTrace } from "./commands/code.js";
|
|
4
3
|
import { runApprove, runNew, runValidate } from "./commands/documents.js";
|
|
5
4
|
import { runInit } from "./commands/init.js";
|
|
6
5
|
import { runInstall } from "./commands/install.js";
|
|
7
|
-
import { runRealign } from "./commands/realign.js";
|
|
8
|
-
import { runFeedback, runJournal } from "./commands/journal.js";
|
|
9
|
-
import { runAbandon, runFeatures, runRoadmap, runTask } from "./commands/progress.js";
|
|
10
6
|
import { runReviewCommand } from "./commands/review.js";
|
|
11
|
-
import { runShow } from "./commands/show.js";
|
|
12
7
|
import { runNext, runStatus } from "./commands/status.js";
|
|
13
8
|
|
|
14
9
|
/** A command, and the options it takes: one list, or one per subcommand for a command that has them. */
|
|
@@ -20,37 +15,15 @@ import { runNext, runStatus } from "./commands/status.js";
|
|
|
20
15
|
|
|
21
16
|
export const COMMANDS = {
|
|
22
17
|
init: { handler: runInit, options: ["name", "yes", "agent", "package"], usage: "init [--name <name>] [--yes] [--agent claude|other] [--package <npm spec>] set up this repository and install the skills" },
|
|
23
|
-
install: { handler: runInstall, options: ["agent"], usage: "install [--agent <agent>]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
approve: {
|
|
34
|
-
handler: runApprove,
|
|
35
|
-
options: ["replan", "rebase", "by-agent", "restamp-unchanged"],
|
|
36
|
-
usage: "approve <vision|architecture|roadmap|ref|path> [--replan <ID>...] [--rebase <ID>...] [--by-agent|--restamp-unchanged] record an approval of a document",
|
|
37
|
-
},
|
|
38
|
-
new: {
|
|
39
|
-
handler: runNew,
|
|
40
|
-
options: { vision: [], glossary: [], architecture: [], roadmap: [], feature: ["title", "roadmap"], change: ["title", "feature", "roadmap"], adr: ["title"], note: ["title", "kind", "about"] },
|
|
41
|
-
usage: "new <vision|glossary|architecture|roadmap> | new <feature|change|adr> <slug> --title <title> [--feature <ref>] [--roadmap <slug>] | new note <slug> --title <title> --kind <input|intake|explore|research> --about <subjects>",
|
|
42
|
-
},
|
|
43
|
-
task: { handler: runTask, options: { done: ["change", "commit"], deviation: ["change"], reopen: ["change"] }, usage: "task <done|deviation|reopen> <T#> [text] [--change <ref>] [--commit <sha>]" },
|
|
44
|
-
features: { handler: runFeatures, options: { add: ["summary", "goals"], update: ["summary"], drop: ["reason"] }, usage: "features <add|update|drop> <slug> [--summary <text>] [--goals <ids>] [--reason <text>]" },
|
|
45
|
-
roadmap: { handler: runRoadmap, options: { prune: [] }, usage: "roadmap prune clear landed items from the roadmap status when a release closes" },
|
|
46
|
-
journal: { handler: runJournal, options: { add: ["set", "item"], list: ["event", "item", "all"] }, usage: "journal <add <event> --set key=value... [--item <ref>]|list [--event <event>] [--item <ref>] [--all]>" },
|
|
47
|
-
review: { handler: runReviewCommand, options: ["item", "round", "blockers", "base"], usage: "review <role> [--item <ref>] [--round 1|2 --blockers <file>] [--base <commit>] run a role in a fresh agent context" },
|
|
48
|
-
feedback: { handler: runFeedback, options: { export: ["out", "no-redact"] }, usage: "feedback export [--out <file>] [--no-redact]" },
|
|
49
|
-
trace: { handler: runTrace, options: ["all", "change"], usage: "trace [--all] [--change <ref>] link scenarios to the tests that prove them" },
|
|
50
|
-
check: { handler: runCheck, options: ["file", "ci"], usage: "check [<file>...] [--ci] enforce layer rules and file budgets" },
|
|
51
|
-
abandon: { handler: runAbandon, options: ["reason"], usage: "abandon <ref> --reason <text> archive a feature or standalone change that stops for good" },
|
|
52
|
-
land: { handler: runLand, options: ["override"], usage: "land [<ref>] [--override <reason>] merge a verified change into the living docs" },
|
|
53
|
-
realign: { handler: runRealign, options: { status: ["full"], done: ["change"] }, usage: "realign <status [--full]|done [--change <ref>]> what the installed Cruze expects of existing code, and recording that the code meets it" },
|
|
18
|
+
install: { handler: runInstall, options: ["agent"], usage: "install [--agent <agent>] install or update the Cruze skills" },
|
|
19
|
+
new: { handler: runNew, options: { vision: [], architecture: [], feature: ["title"] }, usage: "new vision | new architecture | new feature <slug> [--title <title>] start a document from its template" },
|
|
20
|
+
validate: { handler: runValidate, options: [], usage: "validate check every document against the formats" },
|
|
21
|
+
approve: { handler: runApprove, options: [], usage: "approve <vision|architecture|feature slug> record that the user approved a document as it reads now" },
|
|
22
|
+
status: { handler: runStatus, options: [], usage: "status each document's approval, each feature's progress, and the next step" },
|
|
23
|
+
next: { handler: runNext, options: [], usage: "next the step to run next, and why" },
|
|
24
|
+
trace: { handler: runTrace, options: [], usage: "trace [<feature slug>] link scenarios to the tests that carry their IDs" },
|
|
25
|
+
check: { handler: runCheck, options: ["ci"], usage: "check [<file>...] [--ci] enforce layer rules and file budgets" },
|
|
26
|
+
review: { handler: runReviewCommand, options: ["feature", "base"], usage: "review <code-reviewer|researcher> [--feature <slug>] [--base <commit>] run a role in a fresh agent context" },
|
|
54
27
|
};
|
|
55
28
|
|
|
56
29
|
export const USAGE = `Usage: cruze <command> [options]
|
|
@@ -28,36 +28,9 @@ export function parseCommand(argv ) {
|
|
|
28
28
|
help: { type: "boolean", short: "h", default: false },
|
|
29
29
|
title: { type: "string" },
|
|
30
30
|
feature: { type: "string" },
|
|
31
|
-
roadmap: { type: "string" },
|
|
32
|
-
change: { type: "string" },
|
|
33
|
-
commit: { type: "string" },
|
|
34
|
-
summary: { type: "string" },
|
|
35
|
-
goals: { type: "string" },
|
|
36
|
-
reason: { type: "string" },
|
|
37
|
-
set: { type: "string", multiple: true, default: [] },
|
|
38
|
-
item: { type: "string" },
|
|
39
|
-
event: { type: "string" },
|
|
40
|
-
out: { type: "string" },
|
|
41
|
-
"no-redact": { type: "boolean", default: false },
|
|
42
|
-
all: { type: "boolean", default: false },
|
|
43
|
-
file: { type: "string", multiple: true, default: [] },
|
|
44
31
|
ci: { type: "boolean", default: false },
|
|
45
|
-
gate: { type: "string" },
|
|
46
|
-
override: { type: "string" },
|
|
47
|
-
overlap: { type: "boolean", default: false },
|
|
48
|
-
full: { type: "boolean", default: false },
|
|
49
|
-
round: { type: "string" },
|
|
50
32
|
package: { type: "string" },
|
|
51
|
-
replan: { type: "string", multiple: true, default: [] },
|
|
52
|
-
rebase: { type: "string", multiple: true, default: [] },
|
|
53
33
|
base: { type: "string" },
|
|
54
|
-
blockers: { type: "string" },
|
|
55
|
-
"by-agent": { type: "boolean", default: false },
|
|
56
|
-
"restamp-unchanged": { type: "boolean", default: false },
|
|
57
|
-
index: { type: "boolean", default: false },
|
|
58
|
-
notes: { type: "boolean", default: false },
|
|
59
|
-
kind: { type: "string" },
|
|
60
|
-
about: { type: "string" },
|
|
61
34
|
},
|
|
62
35
|
});
|
|
63
36
|
} catch (error) {
|
|
@@ -71,39 +44,12 @@ export function parseCommand(argv ) {
|
|
|
71
44
|
agent: values.agent,
|
|
72
45
|
json: values.json,
|
|
73
46
|
text: values.text,
|
|
74
|
-
set: values.set,
|
|
75
|
-
redact: !values["no-redact"],
|
|
76
|
-
all: values.all,
|
|
77
|
-
file: values.file,
|
|
78
47
|
ci: values.ci,
|
|
79
|
-
overlap: values.overlap,
|
|
80
|
-
full: values.full,
|
|
81
|
-
replan: values.replan,
|
|
82
|
-
rebase: values.rebase,
|
|
83
|
-
byAgent: values["by-agent"],
|
|
84
|
-
restampUnchanged: values["restamp-unchanged"],
|
|
85
|
-
index: values.index,
|
|
86
|
-
notes: values.notes,
|
|
87
48
|
...optional("name", values.name),
|
|
88
49
|
...optional("title", values.title),
|
|
89
50
|
...optional("feature", values.feature),
|
|
90
|
-
...optional("roadmap", values.roadmap),
|
|
91
|
-
...optional("change", values.change),
|
|
92
|
-
...optional("commit", values.commit),
|
|
93
|
-
...optional("summary", values.summary),
|
|
94
|
-
...optional("goals", values.goals),
|
|
95
|
-
...optional("reason", values.reason),
|
|
96
|
-
...optional("item", values.item),
|
|
97
|
-
...optional("event", values.event),
|
|
98
|
-
...optional("out", values.out),
|
|
99
|
-
...optional("gate", values.gate),
|
|
100
|
-
...optional("override", values.override),
|
|
101
|
-
...optional("round", values.round),
|
|
102
51
|
...optional("package", values.package),
|
|
103
52
|
...optional("base", values.base),
|
|
104
|
-
...optional("blockers", values.blockers),
|
|
105
|
-
...optional("kind", values.kind),
|
|
106
|
-
...optional("about", values.about),
|
|
107
53
|
};
|
|
108
54
|
const given = [...new Set(tokens.flatMap((token) => (token.kind === "option" ? [token.name] : [])))];
|
|
109
55
|
return { words: positionals, options, given, help: values.help, version: values.version };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
|
|
4
4
|
|
|
@@ -12,15 +12,6 @@ export class GitRepository {
|
|
|
12
12
|
this.root = root;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
async currentBranch() {
|
|
16
|
-
const branch = await this.git("rev-parse", "--abbrev-ref", "HEAD");
|
|
17
|
-
return branch === null || branch === "HEAD" ? null : branch;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async headCommit() {
|
|
21
|
-
return this.git("rev-parse", "--short", "HEAD");
|
|
22
|
-
}
|
|
23
|
-
|
|
24
15
|
async userName() {
|
|
25
16
|
const name = await this.git("config", "user.name");
|
|
26
17
|
const email = await this.git("config", "user.email");
|
|
@@ -28,72 +19,12 @@ export class GitRepository {
|
|
|
28
19
|
return email === null ? name : `${name} <${email}>`;
|
|
29
20
|
}
|
|
30
21
|
|
|
31
|
-
async otherBranches() {
|
|
32
|
-
const current = await this.currentBranch();
|
|
33
|
-
const listing = await this.git("for-each-ref", "--format=%(refname:short)", "refs/heads", "refs/remotes");
|
|
34
|
-
return (listing ?? "")
|
|
35
|
-
.split("\n")
|
|
36
|
-
.filter((ref) => ref !== "" && ref !== current && !ref.endsWith("/HEAD") && ref !== `origin/${current}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async filesOnBranch(branch , dir ) {
|
|
40
|
-
const listing = await this.git("ls-tree", "-r", "--name-only", branch, "--", dir);
|
|
41
|
-
const paths = (listing ?? "").split("\n").filter((p) => p !== "");
|
|
42
|
-
if (paths.length === 0) return new Map();
|
|
43
|
-
// One git process for every file: a process per file took up to two minutes on a project with 26 branches.
|
|
44
|
-
const blobs = await this.catFiles(paths.map((path) => `${branch}:${path}`));
|
|
45
|
-
return new Map(paths.flatMap((path, i) => (blobs[i] === undefined ? [] : [[path, blobs[i]] ])));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async firstCommitAdding(path , text ) {
|
|
49
|
-
const commits = await this.git("log", "--reverse", "--format=%H", `-S${text}`, "--", path);
|
|
50
|
-
return commits?.split("\n").find((c) => c !== "") ?? null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async fileAt(commit , path ) {
|
|
54
|
-
return (await this.catFiles([`${commit}:${path}`]))[0] ?? null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** The contents of each object, in order, through one `git cat-file --batch`; undefined for one that is missing. */
|
|
58
|
-
catFiles(objects ) {
|
|
59
|
-
return new Promise((resolve) => {
|
|
60
|
-
const child = spawn("git", ["cat-file", "--batch"], { cwd: this.root, stdio: ["pipe", "pipe", "ignore"] });
|
|
61
|
-
const chunks = [];
|
|
62
|
-
child.stdout.on("data", (chunk ) => chunks.push(chunk));
|
|
63
|
-
child.on("error", () => resolve(objects.map(() => undefined)));
|
|
64
|
-
child.on("close", () => resolve(parseBatch(Buffer.concat(chunks), objects.length)));
|
|
65
|
-
child.stdin.end(`${objects.join("\n")}\n`);
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
22
|
async git(...args ) {
|
|
70
23
|
try {
|
|
71
|
-
const { stdout } = await run("git", args, { cwd: this.root
|
|
24
|
+
const { stdout } = await run("git", args, { cwd: this.root });
|
|
72
25
|
return stdout.trimEnd();
|
|
73
26
|
} catch {
|
|
74
27
|
return null;
|
|
75
28
|
}
|
|
76
29
|
}
|
|
77
30
|
}
|
|
78
|
-
|
|
79
|
-
/** Splits `git cat-file --batch` output: a `<sha> <type> <size>` header, the bytes and a newline, or `<name> missing`. */
|
|
80
|
-
function parseBatch(output , count ) {
|
|
81
|
-
const results = [];
|
|
82
|
-
let at = 0;
|
|
83
|
-
while (results.length < count && at < output.length) {
|
|
84
|
-
const headerEnd = output.indexOf(10, at);
|
|
85
|
-
if (headerEnd === -1) break;
|
|
86
|
-
const header = output.subarray(at, headerEnd).toString("utf8");
|
|
87
|
-
const size = /^[0-9a-f]+ \w+ (\d+)$/.exec(header)?.[1];
|
|
88
|
-
if (size === undefined) {
|
|
89
|
-
results.push(undefined);
|
|
90
|
-
at = headerEnd + 1;
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
const start = headerEnd + 1;
|
|
94
|
-
results.push(output.subarray(start, start + Number(size)).toString("utf8"));
|
|
95
|
-
at = start + Number(size) + 1;
|
|
96
|
-
}
|
|
97
|
-
while (results.length < count) results.push(undefined);
|
|
98
|
-
return results;
|
|
99
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { buildProjectDocs, } from "../domain/project/project_docs.js";
|
|
2
|
+
import { CruzeError } from "../domain/cruze_error.js";
|
|
3
|
+
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
@@ -14,34 +14,24 @@ import { journalPath, serializeEntry, } from "../domain/journa
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/** Directories whose files make up the project's documents. */
|
|
18
|
+
const DOC_ROOTS = ["docs", ".cruze"] ;
|
|
19
|
+
|
|
20
|
+
/** Reads every document under docs/ and .cruze/. */
|
|
21
|
+
export async function loadDocs(files ) {
|
|
19
22
|
const snapshot = new Map ();
|
|
20
|
-
for (const root of
|
|
23
|
+
for (const root of DOC_ROOTS) {
|
|
21
24
|
for (const path of await files.listFiles(root)) {
|
|
22
25
|
const text = await files.readText(path);
|
|
23
26
|
if (text !== undefined) snapshot.set(path, text);
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
|
-
|
|
27
|
-
if (readme !== undefined) snapshot.set(PATHS.readme, readme);
|
|
28
|
-
return buildProjectView(snapshot);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** A new view with some files replaced, for checking edits before writing them. */
|
|
32
|
-
export function withFiles(view , changes ) {
|
|
33
|
-
return buildProjectView(new Map([...view.snapshot, ...changes]));
|
|
29
|
+
return buildProjectDocs(snapshot);
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
/**
|
|
37
|
-
export
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
by: (await deps.repository.userName()) ?? "unknown",
|
|
42
|
-
cruze: deps.bundle.version,
|
|
43
|
-
...fields,
|
|
44
|
-
};
|
|
45
|
-
await deps.files.appendText(journalPath(folder), serializeEntry(entry));
|
|
46
|
-
return entry;
|
|
32
|
+
/** The project's config, or an error telling the user to fix it first. */
|
|
33
|
+
export function requireConfig(docs ) {
|
|
34
|
+
const config = docs.config?.config;
|
|
35
|
+
if (config === null || config === undefined) throw new CruzeError("config", "fix .cruze/config.yaml first; run cruze validate for details");
|
|
36
|
+
return config;
|
|
47
37
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { approvalHash, serializeApprovals, withApproval, } from "../../domain/approvals.js";
|
|
2
|
+
import { CruzeError } from "../../domain/cruze_error.js";
|
|
3
|
+
import { parseMarkdown } from "../../domain/markdown.js";
|
|
4
|
+
import { PATHS } from "../../domain/project/layout.js";
|
|
5
|
+
import { resolveDocument } from "../../domain/project/project_docs.js";
|
|
6
|
+
import { validateProject } from "../../domain/validation/validate_project.js";
|
|
7
|
+
import { loadDocs, } from "../project_context.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Records that the user approved a document as it reads now: the vision, the architecture map or a
|
|
11
|
+
* feature. Any later edit to its design content shows it as edited until it is approved again.
|
|
12
|
+
*/
|
|
13
|
+
export async function approveDocument(deps , ref ) {
|
|
14
|
+
const docs = await loadDocs(deps.files);
|
|
15
|
+
const path = resolveDocument(docs, ref);
|
|
16
|
+
if (path === undefined) throw new CruzeError("not-found", `no document matches "${ref}"; name vision, architecture, a feature slug or a path`);
|
|
17
|
+
const errors = validateProject(docs).filter((p) => p.path === path && p.severity === "error");
|
|
18
|
+
if (errors.length > 0) {
|
|
19
|
+
throw new CruzeError("invalid", `${path} has ${errors.length} problem(s) to fix first: ${errors.map((p) => `${p.line === undefined ? "" : `line ${p.line}: `}${p.message}`).join("; ")}`);
|
|
20
|
+
}
|
|
21
|
+
const record = {
|
|
22
|
+
path,
|
|
23
|
+
hash: approvalHash(path, parseMarkdown(docs.snapshot.get(path) ?? "")),
|
|
24
|
+
approvedAt: deps.clock.now().toISOString(),
|
|
25
|
+
by: (await deps.repository.userName()) ?? "unknown",
|
|
26
|
+
};
|
|
27
|
+
await deps.files.writeText(PATHS.approvals, serializeApprovals(withApproval(docs.approvals, record)));
|
|
28
|
+
return record;
|
|
29
|
+
}
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import { factsOf } from "../../domain/elements.js";
|
|
2
1
|
import { checkSource, } from "../../domain/check/check_source.js";
|
|
3
2
|
import { CruzeError } from "../../domain/cruze_error.js";
|
|
4
3
|
import { matchesAny } from "../../domain/glob.js";
|
|
5
|
-
import { FIRST_RELEASE, isPending } from "../../domain/versions/rule_versions.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { resolveChange } from "../../domain/project/artifact_ref.js";
|
|
9
|
-
import { readDelta } from "../../domain/project/deltas.js";
|
|
10
|
-
import { activeChange } from "../../domain/status/work_status.js";
|
|
11
4
|
import { traceScenarios, } from "../../domain/trace.js";
|
|
12
|
-
import {
|
|
5
|
+
import { loadDocs, requireConfig, } from "../project_context.js";
|
|
13
6
|
|
|
14
7
|
|
|
15
8
|
|
|
@@ -18,30 +11,19 @@ import { loadView, } from "../project_context.js";
|
|
|
18
11
|
|
|
19
12
|
|
|
20
13
|
|
|
21
|
-
/**
|
|
22
|
-
* Enforces layer rules and file budgets. Layer violations always fail; budget and
|
|
23
|
-
* module-map warnings fail only with `ci`. A rule newer than the project's `standards:`
|
|
24
|
-
* is pending: it reports, but fails nothing until the project realigns.
|
|
25
|
-
*/
|
|
14
|
+
/** Enforces layer rules and file budgets. Layer violations always fail; budget warnings fail only with `ci`. */
|
|
26
15
|
export async function checkCode(deps , options ) {
|
|
27
|
-
const
|
|
28
|
-
const config = requireConfig(view);
|
|
16
|
+
const config = requireConfig(await loadDocs(deps.files));
|
|
29
17
|
const sources = await readMatching(deps.files, config.source);
|
|
30
|
-
const tests = await readMatching(deps.files, config.tests);
|
|
31
18
|
for (const file of options.files ?? []) {
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
else if (matchesAny(file, config.tests)) tests.set(file, text);
|
|
37
|
-
}
|
|
19
|
+
if (sources.has(file) || !matchesAny(file, config.source)) continue;
|
|
20
|
+
const text = await deps.files.readText(file);
|
|
21
|
+
if (text === undefined) throw new CruzeError("not-found", `${file} does not exist`);
|
|
22
|
+
sources.set(file, text);
|
|
38
23
|
}
|
|
39
|
-
const only = options.files?.filter((file) => sources.has(file)
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
isPending("check", f.rule, standards) ? { ...f, pending: true } : f,
|
|
43
|
-
);
|
|
44
|
-
const failing = findings.filter((f) => f.pending !== true && (f.severity === "error" || options.ci));
|
|
24
|
+
const only = options.files?.filter((file) => sources.has(file));
|
|
25
|
+
const findings = checkSource({ config, sources, ...(only === undefined ? {} : { only }) });
|
|
26
|
+
const failing = findings.filter((f) => f.severity === "error" || options.ci);
|
|
45
27
|
return { passed: failing.length === 0, checked: only?.length ?? sources.size, findings };
|
|
46
28
|
}
|
|
47
29
|
|
|
@@ -50,23 +32,18 @@ export async function checkCode(deps , options
|
|
|
50
32
|
|
|
51
33
|
|
|
52
34
|
|
|
53
|
-
/** Scenario-to-test traceability for
|
|
54
|
-
export async function traceTests(deps ,
|
|
55
|
-
const
|
|
56
|
-
const config = requireConfig(
|
|
57
|
-
let
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
if (
|
|
35
|
+
/** Scenario-to-test traceability for one feature, or for every built feature. */
|
|
36
|
+
export async function traceTests(deps , slug ) {
|
|
37
|
+
const docs = await loadDocs(deps.files);
|
|
38
|
+
const config = requireConfig(docs);
|
|
39
|
+
let required = docs.features.filter((f) => f.status === "built");
|
|
40
|
+
if (slug !== undefined) {
|
|
41
|
+
const feature = docs.features.find((f) => f.slug === slug);
|
|
42
|
+
if (feature === undefined) throw new CruzeError("not-found", `no feature ${slug} in docs/features/`);
|
|
43
|
+
required = [feature];
|
|
61
44
|
}
|
|
62
|
-
const report = traceScenarios(
|
|
63
|
-
return { ...report, passed: report.missing.length === 0 && report.unknown.length === 0, scope:
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function requireConfig(view ) {
|
|
67
|
-
const config = view.config?.config;
|
|
68
|
-
if (config === null || config === undefined) throw new CruzeError("config", "fix .cruze/config.yaml first; run cruze validate for details");
|
|
69
|
-
return config;
|
|
45
|
+
const report = traceScenarios(docs.features, required, await readMatching(deps.files, config.tests));
|
|
46
|
+
return { ...report, passed: report.missing.length === 0 && report.unknown.length === 0, scope: slug ?? "every built feature" };
|
|
70
47
|
}
|
|
71
48
|
|
|
72
49
|
/** Files matching any glob, read from the smallest directories that can contain them. */
|
|
@@ -90,15 +67,3 @@ function globBase(glob ) {
|
|
|
90
67
|
const fixed = segments.slice(0, segments.findIndex((s) => /[*?{]/.test(s)));
|
|
91
68
|
return fixed.join("/");
|
|
92
69
|
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Every module's Path: the living docs' modules, and the modules that work in progress adds or
|
|
96
|
-
* changes in its architecture delta, since their files are written before land merges them.
|
|
97
|
-
*/
|
|
98
|
-
function modulePaths(view ) {
|
|
99
|
-
const living = [...view.living.elements.values()].filter((e) => e.id.startsWith("MOD-")).map((e) => ({ doc: e.doc, element: e.element }));
|
|
100
|
-
const inFlight = view.items
|
|
101
|
-
.filter((item) => item.kind !== "change" && !item.archived)
|
|
102
|
-
.flatMap((item) => readDelta(item.doc).entries.filter((entry) => entry.element.id.startsWith("MOD-") && entry.element.op !== "REMOVED").map((entry) => ({ doc: item.doc, element: entry.element })));
|
|
103
|
-
return [...living, ...inFlight].flatMap(({ doc, element }) => (factsOf(doc, element).get("Path") ?? []).flatMap((value) => [...value.matchAll(/`([^`]+)`/g)].map((m) => m[1] ?? "")));
|
|
104
|
-
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { posix } from "node:path";
|
|
2
2
|
import { CruzeError } from "../../domain/cruze_error.js";
|
|
3
3
|
import { AGENT_SKILL_HOMES, SHARED_SKILLS_DIR, agentSkillHome, knownAgents } from "../../domain/agent_skill_homes.js";
|
|
4
|
+
import { reviewSettingTemplate } from "../../domain/scaffold.js";
|
|
4
5
|
import { SKILL_FILE, installedSkillName, isInstalledSkillName, skillProblems } from "../../domain/skill.js";
|
|
6
|
+
import { reviewCommandProblems } from "../../domain/validation/review_rules.js";
|
|
5
7
|
|
|
6
8
|
|
|
9
|
+
import { loadDocs } from "../project_context.js";
|
|
7
10
|
import { recordVersion, refuseDowngrade, } from "./record_version.js";
|
|
8
11
|
|
|
9
12
|
|
|
@@ -18,6 +21,8 @@ import { recordVersion, refuseDowngrade, } from "./record_ver
|
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
|
|
24
|
+
|
|
25
|
+
|
|
21
26
|
|
|
22
27
|
|
|
23
28
|
/**
|
|
@@ -55,7 +60,8 @@ export async function installSkills(
|
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
const recorded = await recordVersion(files, bundle.version);
|
|
58
|
-
|
|
63
|
+
const notes = reviewCommandProblems(await loadDocs(files), await bundle.template(reviewSettingTemplate("claude"))).map((problem) => problem.message);
|
|
64
|
+
return { version: bundle.version, installed: names, removed, linked, recorded, notes };
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
function assertValid(skills ) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { CruzeError } from "../../domain/cruze_error.js";
|
|
2
|
+
import { isSlug } from "../../domain/ids.js";
|
|
3
|
+
import { PATHS, featurePath } from "../../domain/project/layout.js";
|
|
4
|
+
import { renderTemplate } from "../../domain/scaffold.js";
|
|
5
|
+
import { loadDocs, requireConfig, } from "../project_context.js";
|
|
6
|
+
|
|
7
|
+
export const DOCUMENT_KINDS = ["vision", "architecture", "feature"] ;
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/** Creates the vision, the architecture map or a feature from its template. A feature needs a slug, and a title defaults to it. */
|
|
16
|
+
export async function createDocument(deps , kind , slug , title ) {
|
|
17
|
+
const docs = await loadDocs(deps.files);
|
|
18
|
+
const project = requireConfig(docs).project;
|
|
19
|
+
if (kind === "feature" && (slug === undefined || !isSlug(slug))) throw new CruzeError("invalid-slug", `a feature needs a slug of lowercase words joined by single hyphens, such as "sign-in"`);
|
|
20
|
+
const path = kind === "feature" ? featurePath(slug ?? "") : PATHS[kind];
|
|
21
|
+
if (docs.snapshot.has(path)) throw new CruzeError("exists", `${path} already exists`);
|
|
22
|
+
const values = { project_name: project, slug: slug ?? "", title: title ?? slug ?? "" };
|
|
23
|
+
await deps.files.writeText(path, renderTemplate(await deps.bundle.formatTemplate(`${kind}.md`), values));
|
|
24
|
+
return { kind, path };
|
|
25
|
+
}
|