@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
|
@@ -36,15 +36,9 @@ export function versionSkew(cli: string, recorded: string | undefined): VersionS
|
|
|
36
36
|
* after `version:`, so the file's comments and layout survive.
|
|
37
37
|
*/
|
|
38
38
|
export function withRecordedVersion(configText: string, version: string): string {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
/** The config text with a top-level version key, such as `cruze:` or `standards:`, set in place. */
|
|
43
|
-
export function withVersionLine(configText: string, key: "cruze" | "standards", version: string): string {
|
|
44
|
-
const line = `${key}: ${JSON.stringify(version)}`;
|
|
45
|
-
const existing = new RegExp(`^${key}:.*$`, "m");
|
|
46
|
-
if (existing.test(configText)) return configText.replace(existing, line);
|
|
47
|
-
const anchor = key === "standards" ? /^cruze:.*$/m.exec(configText) ?? /^version:.*$/m.exec(configText) : /^version:.*$/m.exec(configText);
|
|
39
|
+
const line = `cruze: ${JSON.stringify(version)}`;
|
|
40
|
+
if (/^cruze:.*$/m.test(configText)) return configText.replace(/^cruze:.*$/m, line);
|
|
41
|
+
const anchor = /^version:.*$/m.exec(configText);
|
|
48
42
|
if (anchor === null) return `${line}\n${configText}`;
|
|
49
43
|
const end = anchor.index + anchor[0].length;
|
|
50
44
|
return `${configText.slice(0, end)}\n${line}${configText.slice(end)}`;
|
package/src/main.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { ProcessAgentRunner } from "./adapters/outbound/process_agent_runner.ts"
|
|
|
10
10
|
import { SystemClock } from "./adapters/outbound/system_clock.ts";
|
|
11
11
|
import { DefaultAnswerPrompter, TerminalPrompter } from "./adapters/outbound/terminal_prompter.ts";
|
|
12
12
|
|
|
13
|
-
// A reader that stops early, such as `cruze
|
|
13
|
+
// A reader that stops early, such as `cruze status --text | head`, closes the pipe; that is not an error worth a stack trace.
|
|
14
14
|
process.stdout.on("error", (error: NodeJS.ErrnoException) => {
|
|
15
15
|
if (error.code === "EPIPE") process.exit(0);
|
|
16
16
|
throw error;
|
package/templates/init/AGENTS.md
CHANGED
|
@@ -1,28 +1,20 @@
|
|
|
1
1
|
# {{project_name}}
|
|
2
2
|
|
|
3
|
-
Instructions for coding agents working in this repository.
|
|
3
|
+
Instructions for coding agents working in this repository.
|
|
4
4
|
|
|
5
5
|
## Workflow
|
|
6
6
|
|
|
7
|
-
This project uses [Cruze](https://github.com/PrintPractical/cruze)
|
|
7
|
+
This project uses [Cruze](https://github.com/PrintPractical/cruze). `docs/vision.md` says what the product is for and which features the release holds; `docs/architecture.md` maps its ports, adapters and runtime; each `docs/features/<slug>.md` holds one feature's behaviour, public API and stack sketch, approved by the user before it is built. Run `cruze next` to see what comes next. The Cruze skills run the `cruze` command; when it is missing, install it as https://github.com/PrintPractical/cruze#quick-start describes.
|
|
8
8
|
|
|
9
9
|
## Commands
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
No build or test commands yet. `cruze land` fills this in when the foundation change lands.
|
|
13
|
-
<!-- /cruze:managed -->
|
|
11
|
+
No build or test commands yet. The foundation feature fills this in.
|
|
14
12
|
|
|
15
13
|
## Layout
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
Not defined yet. `cruze land` fills this in from the module map in `docs/architecture.md`.
|
|
19
|
-
<!-- /cruze:managed -->
|
|
15
|
+
Not defined yet. The foundation feature fills this in from `docs/architecture.md`.
|
|
20
16
|
|
|
21
17
|
## Conventions
|
|
22
18
|
|
|
23
19
|
- Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/).
|
|
24
20
|
- Record notable changes under `[Unreleased]` in `CHANGELOG.md`.
|
|
25
|
-
|
|
26
|
-
## Known pitfalls
|
|
27
|
-
|
|
28
|
-
None recorded yet. `land` adds a line here when the same review finding or defect about this project's code happens twice.
|
package/templates/init/ci.yml
CHANGED
|
@@ -13,11 +13,11 @@ jobs:
|
|
|
13
13
|
- uses: actions/setup-node@v5
|
|
14
14
|
with:
|
|
15
15
|
node-version: 22
|
|
16
|
-
# The foundation
|
|
17
|
-
# test steps here,
|
|
16
|
+
# The foundation feature adds the language's format, lint, build and
|
|
17
|
+
# test steps here, with a coverage gate.
|
|
18
18
|
- name: Documents match the Cruze formats
|
|
19
19
|
run: npx --yes --package={{cruze_package}} cruze validate
|
|
20
20
|
- name: Layer rules and file budgets
|
|
21
21
|
run: npx --yes --package={{cruze_package}} cruze check --ci
|
|
22
22
|
- name: Every built scenario has a test
|
|
23
|
-
run: npx --yes --package={{cruze_package}} cruze trace
|
|
23
|
+
run: npx --yes --package={{cruze_package}} cruze trace
|
|
@@ -1,33 +1,24 @@
|
|
|
1
1
|
# Cruze project configuration, created by cruze {{cruze_version}}.
|
|
2
|
-
# The format is described in .agents/skills/cruze-formats/
|
|
3
|
-
version:
|
|
2
|
+
# The format is described in .agents/skills/cruze-formats/config.md.
|
|
3
|
+
version: 2
|
|
4
4
|
# The Cruze whose skills this project installed. `cruze install` keeps it current.
|
|
5
5
|
cruze: "{{cruze_version}}"
|
|
6
|
-
# The Cruze whose expectations the code meets. `realign` moves it forward.
|
|
7
|
-
standards: "{{cruze_version}}"
|
|
8
6
|
project: {{project_name_quoted}}
|
|
9
7
|
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# Source files `cruze check` inspects, and files `cruze trace` searches for
|
|
14
|
-
# scenario IDs. Project-scope architecture design sets these.
|
|
8
|
+
# Source files `cruze check` inspects, and test files `cruze trace` searches
|
|
9
|
+
# for scenario IDs. The foundation feature sets these.
|
|
15
10
|
source: []
|
|
16
11
|
tests: []
|
|
17
12
|
|
|
18
|
-
# Budgets
|
|
19
|
-
#
|
|
20
|
-
#
|
|
13
|
+
# Budgets `cruze check` warns on: a backstop against a file that has outgrown
|
|
14
|
+
# one responsibility, not a size to design to. List a file under exceptions,
|
|
15
|
+
# with a reason, to allow it past a budget.
|
|
21
16
|
check:
|
|
22
17
|
max_lines: 600
|
|
23
18
|
max_types: 12
|
|
24
19
|
exceptions: []
|
|
25
20
|
|
|
26
|
-
# Layer rules
|
|
21
|
+
# Layer rules from docs/architecture.md: which layer may import which.
|
|
27
22
|
layers: []
|
|
28
23
|
|
|
29
|
-
# The project's commands, by name, such as `test: cargo test`. The
|
|
30
|
-
# foundation change fills them in; `cruze land` copies them into AGENTS.md.
|
|
31
|
-
commands: {}
|
|
32
|
-
|
|
33
24
|
{{review_setting}}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# How `cruze review` starts an agent in a fresh context to run a role, with the
|
|
2
2
|
# prompt on standard input. This lets Claude Code read the project and run git
|
|
3
|
-
# and cruze.
|
|
4
|
-
#
|
|
3
|
+
# and cruze. To let the reviewer run the tests, add the project's test
|
|
4
|
+
# command, such as "Bash(cargo test:*)". Without review.command, your agent runs each
|
|
5
5
|
# role in a helper of its own.
|
|
6
6
|
review:
|
|
7
7
|
command: ["claude", "-p", "--allowedTools", "Read Grep Glob Bash(git diff:*) Bash(git log:*) Bash(git show:*) Bash(cruze:*)"]
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, resolve } from "node:path";
|
|
3
|
-
import { UsageError } from "./cli_context.js";
|
|
4
|
-
|
|
5
|
-
// Files a person or agent names on the command line, such as a scratch file of review blockers or
|
|
6
|
-
// the path for an exported bundle. They may be anywhere, unlike project files, and relative paths
|
|
7
|
-
// resolve against the working directory.
|
|
8
|
-
|
|
9
|
-
export async function readArgumentFile(path ) {
|
|
10
|
-
try {
|
|
11
|
-
return await readFile(resolve(path), "utf8");
|
|
12
|
-
} catch (error) {
|
|
13
|
-
throw new UsageError(`cannot read ${path}: ${reason(error)}`);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export async function writeArgumentFile(path , text ) {
|
|
18
|
-
try {
|
|
19
|
-
await mkdir(dirname(resolve(path)), { recursive: true });
|
|
20
|
-
await writeFile(resolve(path), text, "utf8");
|
|
21
|
-
} catch (error) {
|
|
22
|
-
throw new UsageError(`cannot write ${path}: ${reason(error)}`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function reason(error ) {
|
|
27
|
-
if (error instanceof Error && "code" in error && typeof error.code === "string") return error.code;
|
|
28
|
-
return error instanceof Error ? error.message : String(error);
|
|
29
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { upstreamDiff, } from "../../../../app/use_cases/upstream_diff.js";
|
|
2
|
-
import { requireArgs, } from "../cli_context.js";
|
|
3
|
-
|
|
4
|
-
export async function runDiff(context , args ) {
|
|
5
|
-
requireArgs(args, ["ref"]);
|
|
6
|
-
const report = await upstreamDiff(context, args[0] ?? "");
|
|
7
|
-
return { json: report, human: render(report) };
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function render(report ) {
|
|
11
|
-
const lines = [`${report.artifact}: ${report.state}`];
|
|
12
|
-
if (report.changes.length === 0) lines.push("Nothing it cites changed since its approval.");
|
|
13
|
-
for (const change of report.changes) {
|
|
14
|
-
lines.push("", `${change.key}${change.path === undefined ? "" : ` in ${change.path}`}:`);
|
|
15
|
-
lines.push(...(change.diff ?? [change.note ?? ""]).map((line) => ` ${line}`));
|
|
16
|
-
}
|
|
17
|
-
if (report.rebase.length > 0) {
|
|
18
|
-
lines.push("", `Its delta changes ${report.rebase.join(", ")}: rewrite the delta to keep the new text, then approve with ${report.rebase.map((id) => `--rebase ${id}`).join(" ")}.`);
|
|
19
|
-
} else if (report.ref !== undefined && report.state === "upstream-changed") {
|
|
20
|
-
lines.push("", `No rebase needed. When the work still holds against this, re-stamp it with cruze approve ${report.ref} --restamp-unchanged.`);
|
|
21
|
-
}
|
|
22
|
-
return lines.join("\n");
|
|
23
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { exportFeedback, listEvents, recordEvent } from "../../../../app/use_cases/journal_events.js";
|
|
2
|
-
import { writeArgumentFile } from "../argument_files.js";
|
|
3
|
-
import { UsageError, requireArgs, } from "../cli_context.js";
|
|
4
|
-
|
|
5
|
-
export async function runJournal(context , args , options ) {
|
|
6
|
-
requireArgs(args, ["add|list"]);
|
|
7
|
-
if (args[0] === "add") {
|
|
8
|
-
requireArgs(args, ["add", "event"]);
|
|
9
|
-
const fields = Object.fromEntries(options.set.map((pair) => {
|
|
10
|
-
const at = pair.indexOf("=");
|
|
11
|
-
if (at < 1) throw new UsageError(`--set takes key=value, not "${pair}"`);
|
|
12
|
-
return [pair.slice(0, at), pair.slice(at + 1)];
|
|
13
|
-
}));
|
|
14
|
-
const entry = await recordEvent(context, args[1] ?? "", fields, options.item);
|
|
15
|
-
return { json: entry, human: `Recorded ${entry.event}.` };
|
|
16
|
-
}
|
|
17
|
-
if (args[0] === "list") {
|
|
18
|
-
const entries = await listEvents(context, options.event, options.item, options.all);
|
|
19
|
-
return { json: entries, human: entries.map((e) => `${e.at} ${e.event} ${JSON.stringify({ ...e, at: undefined, event: undefined, by: undefined })}`).join("\n") || "No entries." };
|
|
20
|
-
}
|
|
21
|
-
throw new UsageError(`cruze journal takes add or list, not "${args[0]}"`);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function runFeedback(context , args , options ) {
|
|
25
|
-
if (args[0] !== "export") throw new UsageError("usage: cruze feedback export [--out <file>] [--no-redact]");
|
|
26
|
-
const bundle = await exportFeedback(context, options.redact);
|
|
27
|
-
if (options.out !== undefined) {
|
|
28
|
-
await writeArgumentFile(options.out, `${JSON.stringify(bundle, null, 2)}\n`);
|
|
29
|
-
return { json: { out: options.out, entries: bundle.entries.length }, human: `Wrote ${bundle.entries.length} entries to ${options.out}.` };
|
|
30
|
-
}
|
|
31
|
-
return { json: bundle, human: `${bundle.entries.length} journal entries${bundle.redacted ? ", redacted" : ""}.` };
|
|
32
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { addFutureFeature, completeTask, dropFutureFeature, updateFeatureSummary, pruneRoadmap, recordDeviation, reopenTask } from "../../../../app/use_cases/record_progress.js";
|
|
2
|
-
import { abandonWork } from "../../../../app/use_cases/abandon_work.js";
|
|
3
|
-
import { UsageError, requireArgs, requireOption, } from "../cli_context.js";
|
|
4
|
-
|
|
5
|
-
export async function runTask(context , args , options ) {
|
|
6
|
-
requireArgs(args, ["done|deviation|reopen", "task"]);
|
|
7
|
-
const change = options.change === undefined ? {} : { change: options.change };
|
|
8
|
-
if (args[0] === "done") {
|
|
9
|
-
const report = await completeTask(context, args[1] ?? "", { ...change, ...(options.commit === undefined ? {} : { commit: options.commit }) });
|
|
10
|
-
return { json: report, human: `${report.task} done in ${report.change} (${report.done}/${report.total}).` };
|
|
11
|
-
}
|
|
12
|
-
if (args[0] === "deviation") {
|
|
13
|
-
requireArgs(args, ["deviation", "task", "text"]);
|
|
14
|
-
const report = await recordDeviation(context, args[1] ?? "", args.slice(2).join(" "), change);
|
|
15
|
-
return { json: report, human: `Recorded a deviation on ${report.task} in ${report.change}.` };
|
|
16
|
-
}
|
|
17
|
-
if (args[0] === "reopen") {
|
|
18
|
-
requireArgs(args, ["reopen", "task", "reason"]);
|
|
19
|
-
const report = await reopenTask(context, args[1] ?? "", args.slice(2).join(" "), change);
|
|
20
|
-
return { json: report, human: `Reopened ${report.task} in ${report.change}.` };
|
|
21
|
-
}
|
|
22
|
-
throw new UsageError(`cruze task takes done, deviation or reopen, not "${args[0]}"`);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function runFeatures(context , args , options ) {
|
|
26
|
-
requireArgs(args, ["add|update|drop", "slug"]);
|
|
27
|
-
if (args[0] === "add") {
|
|
28
|
-
const goals = (options.goals ?? "").split(",").map((g) => g.trim()).filter((g) => g !== "");
|
|
29
|
-
const report = await addFutureFeature(context, args[1] ?? "", requireOption(options.summary, "--summary"), goals);
|
|
30
|
-
return { json: report, human: `Added ${report.feature} to the future list.` };
|
|
31
|
-
}
|
|
32
|
-
if (args[0] === "update") {
|
|
33
|
-
const report = await updateFeatureSummary(context, args[1] ?? "", requireOption(options.summary, "--summary"));
|
|
34
|
-
return { json: report, human: `Updated the summary of ${report.feature} on the ${report.list === "Future" ? "future" : "implemented"} list.` };
|
|
35
|
-
}
|
|
36
|
-
if (args[0] === "drop") {
|
|
37
|
-
const report = await dropFutureFeature(context, args[1] ?? "", requireOption(options.reason, "--reason"));
|
|
38
|
-
return { json: report, human: `Dropped ${report.feature} from the future list.` };
|
|
39
|
-
}
|
|
40
|
-
throw new UsageError(`cruze features takes add, update or drop, not "${args[0]}"`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function runRoadmap(context , args ) {
|
|
44
|
-
requireArgs(args, ["prune"]);
|
|
45
|
-
if (args[0] !== "prune") throw new UsageError(`cruze roadmap takes prune, not "${args[0]}"`);
|
|
46
|
-
const report = await pruneRoadmap(context);
|
|
47
|
-
return { json: report, human: report.removed.length === 0 ? "No landed items to clear." : `Cleared ${report.removed.join(", ")} from the roadmap status.` };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function runAbandon(context , args , options ) {
|
|
51
|
-
requireArgs(args, ["ref"]);
|
|
52
|
-
const report = await abandonWork(context, args[0] ?? "", requireOption(options.reason, "--reason"));
|
|
53
|
-
const back = report.futureList === undefined ? "" : ` ${report.futureList} is back on the future list.`;
|
|
54
|
-
return { json: report, human: `Abandoned ${report.ref}; archived to ${report.archivedTo}.${back}` };
|
|
55
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { realignDone } from "../../../../app/use_cases/realign_done.js";
|
|
2
|
-
import { realignStatus } from "../../../../app/use_cases/realign_status.js";
|
|
3
|
-
import { UsageError, requireArgs, } from "../cli_context.js";
|
|
4
|
-
|
|
5
|
-
export async function runRealign(context , args , options ) {
|
|
6
|
-
requireArgs(args, ["status|done"]);
|
|
7
|
-
if (args[0] === "status") {
|
|
8
|
-
const report = await realignStatus(context, { full: options.full });
|
|
9
|
-
const lines = [`Code meets Cruze ${report.standards}; the installed skills are ${report.target}${report.full ? " (full audit)" : ""}.`];
|
|
10
|
-
for (const note of report.notes) {
|
|
11
|
-
const open = note.findings.filter((f) => !f.waived);
|
|
12
|
-
const how = note.detect !== undefined ? `${open.length} open finding(s) from ${note.detect}` : `for the auditor: ${note.ask ?? ""}`;
|
|
13
|
-
lines.push(`${note.version} ${note.id}: ${note.title} (${how})`);
|
|
14
|
-
for (const f of open) lines.push(` ${f.path}${f.line === undefined ? "" : `:${f.line}`} ${f.message}`);
|
|
15
|
-
}
|
|
16
|
-
if (report.newWorkOnly.length > 0) lines.push(`New work only, nothing to move: ${report.newWorkOnly.join(", ")}`);
|
|
17
|
-
if (report.notes.length === 0) lines.push("Nothing to realign.");
|
|
18
|
-
return { json: report, human: lines.join("\n") };
|
|
19
|
-
}
|
|
20
|
-
if (args[0] === "done") {
|
|
21
|
-
const report = await realignDone(context, options.change === undefined ? {} : { change: options.change });
|
|
22
|
-
const lines = [report.from === report.to ? `The code already meets Cruze ${report.to}.` : `The code now meets Cruze ${report.to}, up from ${report.from}.`];
|
|
23
|
-
if (report.scheduled !== undefined) lines.push(`${report.scheduled} fixes the rest.`);
|
|
24
|
-
if (report.excepted.length > 0) lines.push(`Listed under check.exceptions until it lands: ${report.excepted.join(", ")}`);
|
|
25
|
-
return { json: report, human: lines.join("\n") };
|
|
26
|
-
}
|
|
27
|
-
throw new UsageError(`cruze realign takes status or done, not "${args[0]}"`);
|
|
28
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { showElements, showIndex } from "../../../../app/use_cases/show_elements.js";
|
|
2
|
-
import { showNotes } from "../../../../app/use_cases/show_notes.js";
|
|
3
|
-
|
|
4
|
-
import { showScope, } from "../../../../app/use_cases/show_scope.js";
|
|
5
|
-
import { isId } from "../../../../domain/ids.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { UsageError, requireArgs, } from "../cli_context.js";
|
|
9
|
-
|
|
10
|
-
export async function runShow(context , args , options ) {
|
|
11
|
-
if (options.notes) {
|
|
12
|
-
if (args.length > 1 || options.index) throw new UsageError("--notes takes at most one target: project, a step, a feature slug or a context");
|
|
13
|
-
const report = await showNotes(context, args[0]);
|
|
14
|
-
return { json: report, human: "target" in report ? renderNotesFor(report).join("\n").trimEnd() : renderNotes(report.notes) };
|
|
15
|
-
}
|
|
16
|
-
if (options.index) {
|
|
17
|
-
if (args.length > 1) throw new UsageError("--index takes at most one context or capability");
|
|
18
|
-
const index = await showIndex(context, args[0]);
|
|
19
|
-
return { json: index, human: renderIndex(index) };
|
|
20
|
-
}
|
|
21
|
-
requireArgs(args, ["ref"]);
|
|
22
|
-
if (args.every(isId)) {
|
|
23
|
-
const report = await showElements(context, args);
|
|
24
|
-
return { json: report, human: blocks(report.elements).join("\n").trimEnd() };
|
|
25
|
-
}
|
|
26
|
-
if (args.length > 1) throw new UsageError("show takes one feature or change, or one or more element IDs");
|
|
27
|
-
const report = await showScope(context, args[0] ?? "");
|
|
28
|
-
return { json: report, human: render(report) };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** The index as a list an agent scans for what exists, before fetching bodies with `cruze show <ID>`. */
|
|
32
|
-
function renderIndex(index ) {
|
|
33
|
-
const { architecture, specs } = index.words;
|
|
34
|
-
const lines = [`# Elements`, "", `docs/architecture.md holds ${architecture} words, and docs/specs/ ${specs}. Read an element's body with \`cruze show <ID>\`.`];
|
|
35
|
-
let source = "";
|
|
36
|
-
for (const entry of [...index.entries].sort((a, b) => Number(a.addedBy !== undefined) - Number(b.addedBy !== undefined))) {
|
|
37
|
-
const heading = entry.addedBy === undefined ? entry.source : "In flight, not yet merged";
|
|
38
|
-
if (heading !== source) lines.push("", `## ${heading}`, "");
|
|
39
|
-
source = heading;
|
|
40
|
-
const facts = [
|
|
41
|
-
entry.status,
|
|
42
|
-
entry.place,
|
|
43
|
-
entry.scenarios === undefined ? undefined : `${entry.scenarios} scenarios`,
|
|
44
|
-
entry.addedBy === undefined ? undefined : `added by ${entry.addedBy}`,
|
|
45
|
-
entry.changedBy.length === 0 ? undefined : `changed by ${entry.changedBy.join(", ")}`,
|
|
46
|
-
].filter((f) => f !== undefined);
|
|
47
|
-
lines.push(`- ${entry.id}: ${entry.title}${facts.length === 0 ? "" : ` (${facts.join("; ")})`}`);
|
|
48
|
-
}
|
|
49
|
-
return lines.join("\n");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** The scope as a document an agent reads in place of the living docs. */
|
|
53
|
-
function render(report ) {
|
|
54
|
-
const lines = [`# ${report.ref}${report.feature === undefined ? "" : ` (feature ${report.feature})`}`, ""];
|
|
55
|
-
lines.push("## Elements", "", ...blocks(report.elements), "## Scenarios", "", ...blocks(report.scenarios));
|
|
56
|
-
lines.push("## Cited, not shown", "");
|
|
57
|
-
lines.push(...(report.cited.length === 0 ? ["None."] : report.cited.map((c) => `- ${c.id}: ${c.title} (${c.source})`)), "");
|
|
58
|
-
if (report.feature === undefined) lines.push("## Settled decisions", "", ...(report.decisions.length === 0 ? ["None."] : report.decisions), "");
|
|
59
|
-
else {
|
|
60
|
-
lines.push("## Settled decisions of the feature", "", ...(report.decisions.length === 0 ? ["None."] : report.decisions), "");
|
|
61
|
-
lines.push("## Settled decisions of the change", "", ...(report.changeDecisions.length === 0 ? ["None."] : report.changeDecisions), "");
|
|
62
|
-
}
|
|
63
|
-
lines.push("## Dispositions", "");
|
|
64
|
-
lines.push(...(report.dispositions.length === 0 ? ["None."] : report.dispositions.map((d) => `- [${d.disposition}, ${d.review}] ${d.finding}: ${d.reason}`)), "");
|
|
65
|
-
if (report.references.length > 0) lines.push("## References", "", ...report.references.map((r) => `- \`${r.path}\`: ${r.use}`), "");
|
|
66
|
-
lines.push(...renderNotesFor(report.notes));
|
|
67
|
-
if (report.missing.length > 0) lines.push("## Missing", "", ...report.missing.map((id) => `- ${id} is defined nowhere`), "");
|
|
68
|
-
return lines.join("\n").trimEnd();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function blocks(elements ) {
|
|
72
|
-
if (elements.length === 0) return ["None.", ""];
|
|
73
|
-
return elements.flatMap((element) => [`<!-- ${element.source} -->`, ...element.body.join("\n").trimEnd().split("\n"), ""]);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** The notes about a piece of work and the parts of the user's input handed to it; nothing when there are none. */
|
|
77
|
-
function renderNotesFor(found ) {
|
|
78
|
-
if (found.notes.length === 0 && found.handoffs.length === 0) return [];
|
|
79
|
-
return [
|
|
80
|
-
"## Notes",
|
|
81
|
-
"",
|
|
82
|
-
...found.notes.map((n) => `- \`${n.path}\`: ${n.title}${n.kind === undefined ? "" : ` (${n.kind})`}`),
|
|
83
|
-
...found.handoffs.map((h) => `- ${h.part}, from ${h.source} (${h.status}, handed off in \`${h.note}\`)`),
|
|
84
|
-
"",
|
|
85
|
-
];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function renderNotes(notes ) {
|
|
89
|
-
if (notes.length === 0) return "No notes.";
|
|
90
|
-
return notes
|
|
91
|
-
.flatMap((n) => [
|
|
92
|
-
`- \`${n.path}\`: ${n.title} (${n.kind ?? "no kind"}; about ${n.about.length === 0 ? "nothing named" : n.about.join(", ")})`,
|
|
93
|
-
...n.handoff.map((h) => ` - ${h.part} goes to ${h.goesTo} (${h.status})`),
|
|
94
|
-
])
|
|
95
|
-
.join("\n");
|
|
96
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { CruzeError } from "../../domain/cruze_error.js";
|
|
2
|
-
import { readRows, 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 { appendJournal, loadView, } from "../project_context.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Stops a feature or standalone change for good: archives its folder with the reason, marks it
|
|
16
|
-
* abandoned on the roadmap, and puts a feature back on the future list. Changes that already
|
|
17
|
-
* landed stay in the living docs.
|
|
18
|
-
*/
|
|
19
|
-
export async function abandonWork(deps , ref , reason ) {
|
|
20
|
-
if (reason.trim() === "") throw new CruzeError("missing-reason", "abandoning work needs a reason");
|
|
21
|
-
const view = await loadView(deps.files);
|
|
22
|
-
const { item } = resolveArtifact(view, ref);
|
|
23
|
-
if (item === undefined || item.archived) throw new CruzeError("not-found", `"${ref}" is not an active feature or standalone change`);
|
|
24
|
-
if (item.kind === "change") throw new CruzeError("not-abandonable", `${item.ref} belongs to a feature; remove it from the feature's Changes table through rethink instead`);
|
|
25
|
-
|
|
26
|
-
const slug = typeof item.doc.frontmatter?.["roadmap"] === "string" ? item.doc.frontmatter["roadmap"] : undefined;
|
|
27
|
-
const roadmap = view.snapshot.get(PATHS.roadmap);
|
|
28
|
-
if (slug !== undefined && roadmap !== undefined && roadmap.includes("## Status")) {
|
|
29
|
-
await deps.files.writeText(PATHS.roadmap, upsertRow(roadmap, "Status", [slug, item.folderName, "abandoned"]));
|
|
30
|
-
}
|
|
31
|
-
const report = { ref: item.ref, archivedTo: `${PATHS.archiveDir}/${item.folderName}` };
|
|
32
|
-
const vision = view.snapshot.get(PATHS.vision);
|
|
33
|
-
if (item.kind === "feature" && slug !== undefined && vision !== undefined && vision.includes("### Future")) {
|
|
34
|
-
if (!readRows(vision, "Future").some((row) => row[0] === slug)) {
|
|
35
|
-
await deps.files.writeText(PATHS.vision, upsertRow(vision, "Future", [slug, String(item.doc.frontmatter?.["title"] ?? slug), ""]));
|
|
36
|
-
}
|
|
37
|
-
report.futureList = slug;
|
|
38
|
-
}
|
|
39
|
-
await appendJournal(deps, item.folder, "abandon", { item: item.ref, reason: reason.trim() });
|
|
40
|
-
await deps.files.move(item.folder, report.archivedTo);
|
|
41
|
-
return report;
|
|
42
|
-
}
|