kataster 0.0.0-stage → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/acceptance-tester.md +53 -0
- package/.claude/agents/code-reviewer.md +56 -0
- package/.claude/agents/domain-architect.md +40 -0
- package/.claude/agents/lead-dev.md +50 -0
- package/.claude/agents/product-owner.md +39 -0
- package/.claude/agents/repo-analyst.md +78 -0
- package/.claude/agents/requirements-engineer.md +44 -0
- package/.claude/agents/ux-designer.md +64 -0
- package/.claude/hooks/bash-guard.ts +92 -0
- package/.claude/hooks/code-check.ts +37 -0
- package/.claude/hooks/path-guard.ts +34 -0
- package/.claude/hooks/path-rules.ts +66 -0
- package/.claude/hooks/project-check.ts +108 -0
- package/.claude/hooks/protect-files.ts +55 -0
- package/.claude/hooks/session-start.ts +213 -0
- package/.claude/hooks/stop-gate.ts +56 -0
- package/.claude/hooks/validate-docs.ts +58 -0
- package/.claude/lib/architecture.ts +736 -0
- package/.claude/lib/backlog-github.ts +454 -0
- package/.claude/lib/backlog-jira.ts +347 -0
- package/.claude/lib/backlog-sync.ts +42 -0
- package/.claude/lib/backlog.ts +396 -0
- package/.claude/lib/bazel.ts +212 -0
- package/.claude/lib/config.ts +122 -0
- package/.claude/lib/discovery.ts +821 -0
- package/.claude/lib/engineering.ts +406 -0
- package/.claude/lib/nx.ts +129 -0
- package/.claude/lib/repo.ts +1194 -0
- package/.claude/lib/toolgraph.ts +74 -0
- package/.claude/lib/turbo.ts +166 -0
- package/.claude/package-lock.json +427 -0
- package/.claude/package.json +21 -0
- package/.claude/settings.json +82 -0
- package/.claude/skills/adopt/SKILL.md +283 -0
- package/.claude/skills/adopt/scripts/boundary-check.ts +156 -0
- package/.claude/skills/adopt/scripts/package-doc.ts +71 -0
- package/.claude/skills/adopt/scripts/repo-map.ts +123 -0
- package/.claude/skills/adopt/scripts/rule-doc.ts +80 -0
- package/.claude/skills/architect/ADR-FORMAT.md +97 -0
- package/.claude/skills/architect/SKILL.md +64 -0
- package/.claude/skills/architect/TECH-STACK.md +21 -0
- package/.claude/skills/backlog/SKILL.md +198 -0
- package/.claude/skills/backlog/scripts/backlog-pull.ts +45 -0
- package/.claude/skills/backlog/scripts/backlog-push.ts +38 -0
- package/.claude/skills/backlog/scripts/backlog-status.ts +32 -0
- package/.claude/skills/backlog/scripts/jira-import.ts +40 -0
- package/.claude/skills/backlog/scripts/jira-plan.ts +26 -0
- package/.claude/skills/backlog/scripts/jira-record.ts +41 -0
- package/.claude/skills/bootstrap/SKILL.md +119 -0
- package/.claude/skills/codebase-design/DEEPENING.md +37 -0
- package/.claude/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/.claude/skills/codebase-design/SKILL.md +114 -0
- package/.claude/skills/domain-model/CONTEXT-FORMAT.md +47 -0
- package/.claude/skills/domain-model/SKILL.md +64 -0
- package/.claude/skills/domain-model/templates/data-model.md +29 -0
- package/.claude/skills/engineering-conventions/SKILL.md +31 -0
- package/.claude/skills/event-storming/SKILL.md +44 -0
- package/.claude/skills/event-storming/schema/events.schema.json +124 -0
- package/.claude/skills/event-storming/scripts/render-event-storming.ts +11 -0
- package/.claude/skills/event-storming/scripts/validate-events.ts +11 -0
- package/.claude/skills/event-storming/templates/events.example.yaml +91 -0
- package/.claude/skills/feature/SKILL.md +86 -0
- package/.claude/skills/feature/scripts/intake.ts +356 -0
- package/.claude/skills/grill-me/SKILL.md +7 -0
- package/.claude/skills/grill-with-docs/SKILL.md +7 -0
- package/.claude/skills/grilling/SKILL.md +30 -0
- package/.claude/skills/groom-backlog/SKILL.md +93 -0
- package/.claude/skills/groom-backlog/scripts/backlog-health.ts +238 -0
- package/.claude/skills/implement/SKILL.md +129 -0
- package/.claude/skills/implement/scripts/affected.ts +245 -0
- package/.claude/skills/implement/scripts/check-commands.ts +37 -0
- package/.claude/skills/implement/scripts/check-language.ts +15 -0
- package/.claude/skills/implement/scripts/check-scenarios.ts +59 -0
- package/.claude/skills/implement/scripts/next-story.ts +23 -0
- package/.claude/skills/implement/scripts/story-context.ts +135 -0
- package/.claude/skills/implement/scripts/story-status.ts +47 -0
- package/.claude/skills/implement/scripts/verify.ts +92 -0
- package/.claude/skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/.claude/skills/improve-codebase-architecture/SKILL.md +71 -0
- package/.claude/skills/retro/SKILL.md +53 -0
- package/.claude/skills/review-stories/SKILL.md +31 -0
- package/.claude/skills/shape/SKILL.md +193 -0
- package/.claude/skills/tdd/SKILL.md +38 -0
- package/.claude/skills/tdd/mocking.md +59 -0
- package/.claude/skills/tdd/tests.md +77 -0
- package/.claude/skills/user-stories/SKILL.md +138 -0
- package/.claude/skills/user-stories/schema/story.schema.json +39 -0
- package/.claude/skills/user-stories/scripts/render-backlog.ts +4 -0
- package/.claude/skills/user-stories/scripts/validate-stories.ts +13 -0
- package/.claude/skills/user-stories/templates/story-template.md +38 -0
- package/.claude/skills/ux-design/SKILL.md +107 -0
- package/.claude/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/.claude/skills/writing-for-agents/SKILL.md +81 -0
- package/.claude/tsconfig.json +15 -0
- package/.opencode/agents/acceptance-tester.md +64 -0
- package/.opencode/agents/code-reviewer.md +69 -0
- package/.opencode/agents/domain-architect.md +59 -0
- package/.opencode/agents/lead-dev.md +67 -0
- package/.opencode/agents/product-owner.md +59 -0
- package/.opencode/agents/repo-analyst.md +86 -0
- package/.opencode/agents/requirements-engineer.md +58 -0
- package/.opencode/agents/ux-designer.md +83 -0
- package/.opencode/commands/adopt.md +7 -0
- package/.opencode/commands/backlog-sync.md +7 -0
- package/.opencode/commands/bootstrap.md +7 -0
- package/.opencode/commands/event-storming.md +7 -0
- package/.opencode/commands/feature.md +7 -0
- package/.opencode/commands/grill-me.md +7 -0
- package/.opencode/commands/grill-with-docs.md +7 -0
- package/.opencode/commands/groom-backlog.md +7 -0
- package/.opencode/commands/implement.md +7 -0
- package/.opencode/commands/improve-codebase-architecture.md +7 -0
- package/.opencode/commands/retro.md +7 -0
- package/.opencode/commands/review-stories.md +7 -0
- package/.opencode/commands/shape.md +7 -0
- package/.opencode/opencode.jsonc +15 -0
- package/.opencode/plugins/kataster/index.ts +283 -0
- package/AGENTS.md +21 -0
- package/CHANGELOG.md +91 -0
- package/LICENSE +21 -0
- package/README.md +331 -2
- package/bin/kataster.mjs +7 -0
- package/extras/skills/shadcn/SKILL.md +277 -0
- package/extras/skills/shadcn/agents/openai.yml +5 -0
- package/extras/skills/shadcn/assets/shadcn-small.png +0 -0
- package/extras/skills/shadcn/assets/shadcn.png +0 -0
- package/extras/skills/shadcn/cli.md +290 -0
- package/extras/skills/shadcn/customization.md +209 -0
- package/extras/skills/shadcn/evals/evals.json +77 -0
- package/extras/skills/shadcn/mcp.md +105 -0
- package/extras/skills/shadcn/registry.md +277 -0
- package/extras/skills/shadcn/rules/base-vs-radix.md +306 -0
- package/extras/skills/shadcn/rules/chat.md +250 -0
- package/extras/skills/shadcn/rules/composition.md +213 -0
- package/extras/skills/shadcn/rules/forms.md +192 -0
- package/extras/skills/shadcn/rules/icons.md +101 -0
- package/extras/skills/shadcn/rules/styling.md +185 -0
- package/guide/README.md +9 -0
- package/guide/brownfield.md +242 -0
- package/guide/concepts.md +109 -0
- package/guide/greenfield.md +84 -0
- package/guide/install.md +144 -0
- package/guide/release.md +92 -0
- package/package.json +56 -4
- package/src/cli.mjs +302 -0
- package/src/detect.mjs +237 -0
- package/src/install.mjs +427 -0
- package/src/repo.mjs +129 -0
- package/src/util.mjs +110 -0
- package/templates/CONTEXT.md +21 -0
- package/templates/conventions.md +63 -0
- package/templates/docs/adr/README.md +7 -0
- package/templates/docs/architecture/data-model.md +31 -0
- package/templates/docs/domain/context-map.md +21 -0
- package/templates/docs/domain/events.yaml +23 -0
- package/templates/docs/product/initiatives/README.md +41 -0
- package/templates/docs/product/ux-guidelines.md +27 -0
- package/templates/docs/product/vision.md +36 -0
- package/templates/docs/reviews/.gitkeep +0 -0
- package/templates/docs/stories/OPEN_QUESTIONS.md +8 -0
- package/templates/docs/stories/README.md +26 -0
- package/templates/docs/workflow.md +135 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds the deterministic repository map that `/adopt` works from.
|
|
3
|
+
*
|
|
4
|
+
* node .claude/skills/adopt/scripts/repo-map.ts # write map + root AGENTS block
|
|
5
|
+
* node .claude/skills/adopt/scripts/repo-map.ts --json # print the map, write nothing
|
|
6
|
+
* node .claude/skills/adopt/scripts/repo-map.ts --check # exit 1 when the map is stale
|
|
7
|
+
* node .claude/skills/adopt/scripts/repo-map.ts --adopt <path>
|
|
8
|
+
*
|
|
9
|
+
* The map is generated from manifests and git history. It is not a domain model: it says where code lives,
|
|
10
|
+
* how packages depend on each other and which slice to adopt first. Domain meaning comes from the grilling.
|
|
11
|
+
*/
|
|
12
|
+
import { ROOT, findPackage, markAdopted, packageCounts, proposeSlices, readRepoMap, scanRepo, stableMap, updateProjectTooling, writeRepoMap, writeRootBlock } from "../../../lib/repo.ts";
|
|
13
|
+
import { graphLabel, graphToolLabel } from "../../../lib/toolgraph.ts";
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const root = ROOT;
|
|
17
|
+
|
|
18
|
+
function usage(): void {
|
|
19
|
+
console.log(
|
|
20
|
+
[
|
|
21
|
+
"Usage: node .claude/skills/adopt/scripts/repo-map.ts [--json|--check|--adopt <path>]",
|
|
22
|
+
"",
|
|
23
|
+
" (no option) write docs/architecture/repo-map.{json,md} and the root AGENTS.md block",
|
|
24
|
+
" --json print the map as JSON without writing",
|
|
25
|
+
" --check exit 1 when the checked-in map no longer matches the repository",
|
|
26
|
+
" --adopt <path> mark a package as adopted (after the grilling)",
|
|
27
|
+
" --no-tool-graph skip the build-tool graph query (Turbo/Nx/Bazel); manifest graph only",
|
|
28
|
+
].join("\n"),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
33
|
+
usage();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const adoptIndex = args.indexOf("--adopt");
|
|
38
|
+
if (adoptIndex !== -1) {
|
|
39
|
+
const path = args[adoptIndex + 1];
|
|
40
|
+
if (!path) {
|
|
41
|
+
console.error("--adopt needs a package path, e.g. apps/billing");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const map = markAdopted(root, path);
|
|
46
|
+
const pkg = findPackage(map, path);
|
|
47
|
+
console.log(`Adopted: ${pkg?.path ?? path}${pkg?.name ? ` (${pkg.name})` : ""}`);
|
|
48
|
+
console.log("The root AGENTS.md block now lists it as adopted.");
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const map = scanRepo(root, {
|
|
57
|
+
// --check stays fast and environment-independent; the graph is enrichment, not part of the stable map.
|
|
58
|
+
toolGraph: !args.includes("--check") && !args.includes("--no-tool-graph"),
|
|
59
|
+
});
|
|
60
|
+
// Refreshing the map must not forget which slices were already adopted.
|
|
61
|
+
const previous = readRepoMap(root);
|
|
62
|
+
if (previous) {
|
|
63
|
+
for (const pkg of map.packages) {
|
|
64
|
+
const old = previous.packages.find((p) => p.path === pkg.path);
|
|
65
|
+
if (old?.adopted) {
|
|
66
|
+
pkg.adopted = true;
|
|
67
|
+
pkg.adoptedAt = old.adoptedAt;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (args.includes("--json")) {
|
|
73
|
+
console.log(JSON.stringify(map, null, 2));
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (args.includes("--check")) {
|
|
78
|
+
const existing = readRepoMap(root);
|
|
79
|
+
if (!existing) {
|
|
80
|
+
console.error("No docs/architecture/repo-map.json yet – run repo-map.ts once.");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
if (stableMap(existing) === stableMap(map)) {
|
|
84
|
+
console.log("OK – the repository map matches the manifests.");
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const before = new Map(existing.packages.map((p) => [p.path, p]));
|
|
89
|
+
const after = new Map(map.packages.map((p) => [p.path, p]));
|
|
90
|
+
const added = [...after.keys()].filter((p) => !before.has(p));
|
|
91
|
+
const removed = [...before.keys()].filter((p) => !after.has(p));
|
|
92
|
+
const changed = [...after.keys()].filter((p) => before.has(p) && JSON.stringify(before.get(p)) !== JSON.stringify(after.get(p)));
|
|
93
|
+
console.error("The repository map is stale – run `node .claude/skills/adopt/scripts/repo-map.ts` and review the diff.");
|
|
94
|
+
if (added.length) console.error(` added: ${added.join(", ")}`);
|
|
95
|
+
if (removed.length) console.error(` removed: ${removed.join(", ")}`);
|
|
96
|
+
if (changed.length) console.error(` changed: ${changed.join(", ")}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const written = writeRepoMap(root, map);
|
|
101
|
+
const profileUpdated = updateProjectTooling(root, map.tooling);
|
|
102
|
+
const agentsUpdated = writeRootBlock(root, map);
|
|
103
|
+
const proposal = proposeSlices(map);
|
|
104
|
+
|
|
105
|
+
console.log(`Repository map written: ${written.json.replace(`${root}/`, "")}, ${written.md.replace(`${root}/`, "")}`);
|
|
106
|
+
console.log(`Tooling: ${map.tooling.kind === "none" ? "none (single package)" : map.tooling.kind}${map.tooling.config ? ` (${map.tooling.config})` : ""}`);
|
|
107
|
+
if (map.toolGraph) {
|
|
108
|
+
console.log(`Tool graph: ${graphLabel(map.toolGraph)}${map.toolGraph.task ? ` (${map.toolGraph.task})` : ""} – ${map.toolGraph.packages.length} package(s), ${map.toolGraph.edges.length} edge(s)`);
|
|
109
|
+
} else {
|
|
110
|
+
const tool = graphToolLabel(map.tooling.kind);
|
|
111
|
+
if (tool) console.log(`Tool graph: ${tool} graph not available – using the manifest graph`);
|
|
112
|
+
}
|
|
113
|
+
console.log(`Packages: ${map.packages.length} (${packageCounts(map)})`);
|
|
114
|
+
if (profileUpdated) console.log("Updated .claude/project.json → repo.tooling");
|
|
115
|
+
if (agentsUpdated) console.log("Updated the generated repository block in AGENTS.md");
|
|
116
|
+
|
|
117
|
+
if (proposal.domain) {
|
|
118
|
+
console.log("");
|
|
119
|
+
const label = proposal.domain.path === "." ? "root package" : proposal.domain.path;
|
|
120
|
+
console.log(`Suggested first slice: ${label} (${proposal.domain.name})`);
|
|
121
|
+
if (proposal.library) console.log(` with shared library: ${proposal.library.path} (${proposal.library.name})`);
|
|
122
|
+
console.log(`Mark it adopted after the grilling: node .claude/skills/adopt/scripts/repo-map.ts --adopt ${proposal.domain.path}`);
|
|
123
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffolds and validates `.claude/rules/*.md` – path-scoped rules with `paths:` frontmatter.
|
|
3
|
+
*
|
|
4
|
+
* node .claude/skills/adopt/scripts/rule-doc.ts libs-shared --paths "libs/**" --title "Shared libraries"
|
|
5
|
+
* node .claude/skills/adopt/scripts/rule-doc.ts --check
|
|
6
|
+
* node .claude/skills/adopt/scripts/rule-doc.ts --list
|
|
7
|
+
*
|
|
8
|
+
* A rule for a whole directory should be `<dir>/AGENTS.md` instead – that is the mechanism both harnesses load
|
|
9
|
+
* on demand. `.claude/rules/` is for patterns a directory cannot express (e.g. every SQL file). Claude Code reads
|
|
10
|
+
* these files natively; OpenCode's plugin injects a matching rule when a file under its `paths:` is touched.
|
|
11
|
+
*/
|
|
12
|
+
import { relPath, validateRules, writeRule } from "../../../lib/architecture.ts";
|
|
13
|
+
import { ROOT } from "../../../lib/repo.ts";
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
|
|
17
|
+
function usage(): void {
|
|
18
|
+
console.log(
|
|
19
|
+
[
|
|
20
|
+
"Usage: node .claude/skills/adopt/scripts/rule-doc.ts <slug> --paths <glob> [--paths <glob>] [--title <title>]",
|
|
21
|
+
" node .claude/skills/adopt/scripts/rule-doc.ts --check",
|
|
22
|
+
" node .claude/skills/adopt/scripts/rule-doc.ts --list",
|
|
23
|
+
].join("\n"),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
28
|
+
usage();
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (args.includes("--check")) {
|
|
33
|
+
const { rules, errors } = validateRules(ROOT);
|
|
34
|
+
for (const error of errors) console.error(`ERROR ${error}`);
|
|
35
|
+
if (errors.length) process.exit(1);
|
|
36
|
+
console.log(`OK – ${rules.length} rule file${rules.length === 1 ? "" : "s"} valid.`);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (args.includes("--list")) {
|
|
41
|
+
const { rules, errors } = validateRules(ROOT);
|
|
42
|
+
for (const rule of rules) console.log(`${rule.path}: ${rule.paths.join(", ")} – ${rule.title}`);
|
|
43
|
+
if (errors.length) {
|
|
44
|
+
for (const error of errors) console.error(`ERROR ${error}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const slug = args.find((arg) => !arg.startsWith("--"));
|
|
51
|
+
if (!slug) {
|
|
52
|
+
usage();
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const paths: string[] = [];
|
|
57
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
58
|
+
if (args[i] !== "--paths") continue;
|
|
59
|
+
const value = args[i + 1];
|
|
60
|
+
if (!value || value.startsWith("--")) {
|
|
61
|
+
console.error("--paths needs a glob, e.g. --paths \"libs/**\"");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
paths.push(...value.split(",").map((p) => p.trim()).filter(Boolean));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const titleIndex = args.indexOf("--title");
|
|
68
|
+
const fallbackTitle = slug.replaceAll("-", " ").replace(/^\w/, (c) => c.toUpperCase());
|
|
69
|
+
const title = titleIndex !== -1 && args[titleIndex + 1] && !args[titleIndex + 1].startsWith("--")
|
|
70
|
+
? args[titleIndex + 1]
|
|
71
|
+
: fallbackTitle;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const path = writeRule(ROOT, slug, paths, title);
|
|
75
|
+
console.log(`Created ${relPath(ROOT, path)}.`);
|
|
76
|
+
console.log("Fill the rule body during the grilling; one rule per file, and name its enforcement point.");
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# ADR Format
|
|
2
|
+
|
|
3
|
+
ADRs live in `docs/adr/` and use sequential numbering: `0001-slug.md`, `0002-slug.md`, etc. Written in English.
|
|
4
|
+
|
|
5
|
+
## Template
|
|
6
|
+
|
|
7
|
+
```md
|
|
8
|
+
---
|
|
9
|
+
status: proposed
|
|
10
|
+
date: YYYY-MM-DD
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# {Short title of the decision}
|
|
14
|
+
|
|
15
|
+
{1-3 sentences: what's the context, what did we decide, and why. Cite the driving requirement with its path, e.g. `docs/product/vision.md`.}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
That's it. An ADR can be a single paragraph. The value is in recording *that* a decision was made and *why*, not in filling out sections.
|
|
19
|
+
|
|
20
|
+
## Status
|
|
21
|
+
|
|
22
|
+
The `status` frontmatter is mandatory, because decisions here go through the user:
|
|
23
|
+
|
|
24
|
+
- `proposed` – every new ADR, whoever writes it (main session or `domain-architect`)
|
|
25
|
+
- `accepted` – **set only by the user**
|
|
26
|
+
- `rejected`
|
|
27
|
+
- `superseded by ADR-NNNN` – accepted ADRs are never edited; a new ADR replaces them
|
|
28
|
+
|
|
29
|
+
## Optional sections
|
|
30
|
+
|
|
31
|
+
Only include these when they add genuine value. Most ADRs won't need them.
|
|
32
|
+
|
|
33
|
+
- **Considered Options**: only when the rejected alternatives are worth remembering
|
|
34
|
+
- **Consequences**: only when non-obvious downstream effects need to be called out (incl. follow-up spikes or tech tasks)
|
|
35
|
+
|
|
36
|
+
## Large decisions
|
|
37
|
+
|
|
38
|
+
For decisions with several serious options and many criteria (e.g. the tech stack, see [TECH-STACK.md](./TECH-STACK.md)), use the full form:
|
|
39
|
+
|
|
40
|
+
```md
|
|
41
|
+
---
|
|
42
|
+
status: proposed
|
|
43
|
+
date: YYYY-MM-DD
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
# NNNN – {Short title}
|
|
47
|
+
|
|
48
|
+
## Context and Problem
|
|
49
|
+
{Which question is being decided, and why now?}
|
|
50
|
+
|
|
51
|
+
## Driving Requirements
|
|
52
|
+
- {Requirement} – source: `docs/...`
|
|
53
|
+
|
|
54
|
+
## Considered Options
|
|
55
|
+
1. {Option A}
|
|
56
|
+
2. {Option B}
|
|
57
|
+
|
|
58
|
+
## Evaluation
|
|
59
|
+
| Criterion | Option A | Option B |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| … | … | … |
|
|
62
|
+
|
|
63
|
+
## Decision
|
|
64
|
+
{Chosen option} – because {reasoning that follows from the evaluation}.
|
|
65
|
+
|
|
66
|
+
## Consequences
|
|
67
|
+
- Positive: …
|
|
68
|
+
- Negative / cost: …
|
|
69
|
+
- Follow-up work (spikes, tech tasks): …
|
|
70
|
+
|
|
71
|
+
## Open Points
|
|
72
|
+
- HS-… / questions
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Numbering
|
|
76
|
+
|
|
77
|
+
Scan `docs/adr/` for the highest existing number and increment by one.
|
|
78
|
+
|
|
79
|
+
## When to offer an ADR
|
|
80
|
+
|
|
81
|
+
All three of these must be true:
|
|
82
|
+
|
|
83
|
+
1. **Hard to reverse**: the cost of changing your mind later is meaningful
|
|
84
|
+
2. **Surprising without context**: a future reader will look at the system and wonder "why on earth did they do it this way?"
|
|
85
|
+
3. **The result of a real trade-off**: there were genuine alternatives and you picked one for specific reasons
|
|
86
|
+
|
|
87
|
+
If a decision is easy to reverse, skip it: you'll just reverse it. If it's not surprising, nobody will wonder why. If there was no real alternative, there's nothing to record beyond "we did the obvious thing."
|
|
88
|
+
|
|
89
|
+
### What qualifies
|
|
90
|
+
|
|
91
|
+
- **Architectural shape.** "The product is a single deployable PWA." "Cases are stored as an event history; their status is projected from it."
|
|
92
|
+
- **Integration patterns between contexts.** "Ordering and fulfilment communicate via domain events, not direct calls."
|
|
93
|
+
- **Technology choices that carry lock-in.** Database, auth provider, hosting, offline storage. Not every library: just the ones that would take a quarter to swap out.
|
|
94
|
+
- **Boundary and scope decisions.** "Product master data is owned by the catalogue context; others reference it by ID only." The explicit no-s are as valuable as the yes-s.
|
|
95
|
+
- **Deliberate deviations from the obvious path.** Anything where a reasonable reader would assume the opposite. These stop the next engineer from "fixing" something that was deliberate.
|
|
96
|
+
- **Constraints not visible in the system.** "Customers must be able to submit without an account." "The warehouse has no reliable Wi-Fi."
|
|
97
|
+
- **Rejected alternatives when the rejection is non-obvious.** Otherwise someone will suggest them again in six months.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architect
|
|
3
|
+
description: Build and sharpen the project's architecture and record decisions as ADRs in docs/adr/ (incl. the tech stack decision). Use when discussing architectural shape, integration between bounded contexts, technology choices, or when recording or editing an ADR.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Architecture
|
|
7
|
+
|
|
8
|
+
Actively build and sharpen the architecture as you design. This is the *active* discipline: challenging decisions, inventing scenarios that stress them, and writing decisions down the moment they crystallise. (Merely *reading* ADRs for context is not this skill. This skill is for when you're changing the architecture, not just consuming it.)
|
|
9
|
+
|
|
10
|
+
Domain language (the glossary in `CONTEXT.md`), bounded contexts, aggregates and the logical data model belong to the `domain-model` skill. Architecture builds on them: the domain comes first, technology follows.
|
|
11
|
+
|
|
12
|
+
## File structure
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
/
|
|
16
|
+
├── CONTEXT.md ← glossary (domain-model skill)
|
|
17
|
+
└── docs/
|
|
18
|
+
├── adr/ ← architecture decisions (this skill)
|
|
19
|
+
│ ├── 0001-tech-stack.md
|
|
20
|
+
│ └── 0002-...
|
|
21
|
+
├── architecture/
|
|
22
|
+
│ └── data-model.md ← logical data model (domain-model skill)
|
|
23
|
+
└── domain/
|
|
24
|
+
├── context-map.md
|
|
25
|
+
└── events.yaml
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Once code exists and contexts live in separate modules, context-specific decisions may move next to the code (`src/<context>/docs/adr/`); system-wide decisions stay in `docs/adr/`.
|
|
29
|
+
|
|
30
|
+
## During the session
|
|
31
|
+
|
|
32
|
+
### Challenge against existing decisions
|
|
33
|
+
|
|
34
|
+
When a proposal conflicts with an accepted ADR, call it out immediately: "ADR 0002 says reports work offline, but this assumes a live connection. Do we supersede 0002?" Accepted ADRs are never edited; they are superseded by a new one.
|
|
35
|
+
|
|
36
|
+
### Trace every decision back to a requirement
|
|
37
|
+
|
|
38
|
+
Architecture follows documented needs, not taste. Every decision cites its driving requirement with a path (`docs/product/vision.md`, `docs/domain/events.yaml`, a story, a hotspot). No documented requirement → no extra complexity. If the requirement is missing, it's a question for the user or a hotspot, not an assumption.
|
|
39
|
+
|
|
40
|
+
### Discuss concrete scenarios
|
|
41
|
+
|
|
42
|
+
Stress-test decisions with specific scenarios: "A field worker is in a basement without connectivity and wants to record a result – what happens?" "Two people close the same case at the same time." Force precision about the boundaries where the architecture could break.
|
|
43
|
+
|
|
44
|
+
### Cross-reference with docs and code
|
|
45
|
+
|
|
46
|
+
When the user states how something works, check `docs/` (and the code, once it exists) for agreement. Surface contradictions: "The data model has one owner per item, but you just said items can be lent with a second owner. Which is right?"
|
|
47
|
+
|
|
48
|
+
### Prefer the simplest architecture
|
|
49
|
+
|
|
50
|
+
The simplest architecture that meets the documented requirements is the default. Distributed systems, separate apps or microservices each need a concrete, documented requirement as justification.
|
|
51
|
+
|
|
52
|
+
### Offer ADRs sparingly
|
|
53
|
+
|
|
54
|
+
Only offer to create an ADR when all three are true:
|
|
55
|
+
|
|
56
|
+
1. **Hard to reverse**: the cost of changing your mind later is meaningful
|
|
57
|
+
2. **Surprising without context**: a future reader will wonder "why did they do it this way?"
|
|
58
|
+
3. **The result of a real trade-off**: there were genuine alternatives and you picked one for specific reasons
|
|
59
|
+
|
|
60
|
+
If any of the three is missing, skip the ADR. Use the format in [ADR-FORMAT.md](./ADR-FORMAT.md). New ADRs always start as `proposed`; **only the user sets `accepted`**.
|
|
61
|
+
|
|
62
|
+
### The tech stack decision
|
|
63
|
+
|
|
64
|
+
Decide only after the event storming and a rough domain model exist; before that the facts are missing. Follow [TECH-STACK.md](./TECH-STACK.md).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Tech Stack Decision
|
|
2
|
+
|
|
3
|
+
Decide only once the event storming and a rough domain model exist. Before that, the facts are missing. Record the result as a large ADR (see [ADR-FORMAT.md](./ADR-FORMAT.md)), `status: proposed`.
|
|
4
|
+
|
|
5
|
+
## Criteria (evaluate each with evidence from `docs/`)
|
|
6
|
+
|
|
7
|
+
1. **On-site use**: Is work done on a device (phone/tablet)? What is the network coverage (Wi-Fi in the hall)? Offline required?
|
|
8
|
+
2. **Device features**: Camera, QR/barcode scanning, push notifications, offline use – is the browser (PWA) enough, or are native apps needed?
|
|
9
|
+
3. **User groups & access**: Public reporting (e.g. visitors) vs. internal team, roles, authentication.
|
|
10
|
+
4. **Other consumers**: Are there concrete additional clients or integrations today that justify a standalone API?
|
|
11
|
+
5. **Team & maintenance**: Who develops and runs this long-term? Which languages does the team know?
|
|
12
|
+
6. **Operations**: Hosting, cost, backups, updates – as few moving parts as possible.
|
|
13
|
+
7. **Future-proofing without up-front cost**: Does the option allow later expansion without paying for it today?
|
|
14
|
+
|
|
15
|
+
## Evaluate at least these options
|
|
16
|
+
|
|
17
|
+
- **A – Full-stack monolith as a PWA** (e.g. Next.js with a cleanly separated domain layer). One deployment, usable on mobile via browser/PWA.
|
|
18
|
+
- **B – Separate API (Go or TypeScript) + web app + mobile app**. Maximum decoupling, three codebases and release processes.
|
|
19
|
+
- **C – Hybrid**: monolith with a clearly separated domain/API layer now; extract an API or build a native app only when there is concrete need.
|
|
20
|
+
|
|
21
|
+
Evaluation as a table (criterion × option, with reasoning). The recommendation must follow from the table. Name effort differences explicitly. Uncertainties → propose a spike story.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backlog
|
|
3
|
+
description: Keeps docs/stories/ in sync with the configured backlog backend – local (default), GitHub Issues via gh, or Jira via mcp-atlassian.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
argument-hint: "[status | pull | push] [ST-NNN ...]"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Backlog Backend
|
|
9
|
+
|
|
10
|
+
`docs/stories/` is the exchange format, not necessarily the backlog. `.claude/project.json` decides where the source
|
|
11
|
+
of truth lives:
|
|
12
|
+
|
|
13
|
+
```jsonc
|
|
14
|
+
"backlog": {
|
|
15
|
+
"provider": "local", // local | github | jira
|
|
16
|
+
"github": { "repo": "", "labelPrefix": "kataster" },
|
|
17
|
+
"jira": {
|
|
18
|
+
"mcpServer": "mcp-atlassian",
|
|
19
|
+
"project": "PROJ",
|
|
20
|
+
"issueType": "Story",
|
|
21
|
+
"labelPrefix": "kataster",
|
|
22
|
+
"statusMap": {
|
|
23
|
+
"draft": "To Do", "review": "In Review", "ready": "Ready",
|
|
24
|
+
"in-progress": "In Progress", "done": "Done", "superseded": "Won't Do"
|
|
25
|
+
},
|
|
26
|
+
"priorityMap": { "must": "Highest", "should": "High", "could": "Medium", "wont": "Lowest" }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Provider | Source of truth | Transport |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `local` | `docs/stories/` | none – the default, nothing changes |
|
|
34
|
+
| `github` | GitHub Issues | `gh` CLI (deterministic, works from scripts) |
|
|
35
|
+
| `jira` | Jira | `mcp-atlassian` (MCP is agent-only; the scripts prepare and consume the payloads) |
|
|
36
|
+
|
|
37
|
+
**With a tracker, the tracker leads.** `docs/stories/` is a local mirror that keeps validation, test traceability and
|
|
38
|
+
offline work intact. You may create and edit stories there; publish them through the sync. A change made in the
|
|
39
|
+
tracker is pulled into the mirror.
|
|
40
|
+
|
|
41
|
+
## Running a sync
|
|
42
|
+
|
|
43
|
+
- empty or `status` → `backlog-status.ts`: what is linked, what drifted.
|
|
44
|
+
- `pull` → GitHub: `backlog-pull.ts`; Jira: fetch with MCP, write the JSON, `backlog-pull.ts --from`.
|
|
45
|
+
- `push [ST-NNN …]` → GitHub: `backlog-push.ts`; Jira: `jira-plan.ts` → MCP → `jira-record.ts` → plan again for links.
|
|
46
|
+
- A single story argument limits push/plan to that story; pull processes every issue in the import.
|
|
47
|
+
|
|
48
|
+
## What syncs
|
|
49
|
+
|
|
50
|
+
Only the backlog core:
|
|
51
|
+
|
|
52
|
+
- the story body and `title`, `type`, `context`, `priority`, `size`, `risk`, `events`, `depends_on`, `labels`, `status`
|
|
53
|
+
- the external reference, `external: { provider, key, url }`
|
|
54
|
+
- nothing else: `docs/reviews/`, `docs/stories/OPEN_QUESTIONS.md`, ADRs and `BACKLOG.md` stay local. `BACKLOG.md` is
|
|
55
|
+
generated from the mirror and never synced.
|
|
56
|
+
|
|
57
|
+
The story keeps its stable `ST-NNN` id; the tracker key lives in `external`. Test titles (`ST-NNN: <scenario>`) and the
|
|
58
|
+
domain IDs therefore do not change when a tracker is added. **Never renumber a story, never write `external` by hand.**
|
|
59
|
+
|
|
60
|
+
The issue body is the story body plus a machine block:
|
|
61
|
+
|
|
62
|
+
- GitHub: `<!-- kataster:story … -->` (hidden in the rendered issue)
|
|
63
|
+
- Jira: a ```` ```kataster-story … ``` ```` code block (Jira Cloud drops HTML comments when it converts Markdown to ADF)
|
|
64
|
+
|
|
65
|
+
The block restores the fields the tracker has no field for. The tracker wins for the fields it does have: status,
|
|
66
|
+
priority and the `kataster-*` labels.
|
|
67
|
+
|
|
68
|
+
## GitHub
|
|
69
|
+
|
|
70
|
+
`gh` must be installed and authenticated (`gh auth login`, `repo` scope). `repo` may be empty – then `gh` uses the
|
|
71
|
+
repository it runs in. Labels are created on demand.
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node .claude/skills/backlog/scripts/backlog-status.ts # what is linked, what drifted (--check exits 1)
|
|
75
|
+
node .claude/skills/backlog/scripts/backlog-push.ts # create/update issues from the mirror
|
|
76
|
+
node .claude/skills/backlog/scripts/backlog-pull.ts # import issue changes into the mirror
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A story without `external` is created by `push`; an issue whose title starts with `ST-NNN:`, that carries the
|
|
80
|
+
`kataster` label, or that contains the machine block is picked up by `pull`. `done` and `superseded` close the issue;
|
|
81
|
+
`superseded` closes as "not planned". A status change through `story-status.ts` also updates the linked issue.
|
|
82
|
+
|
|
83
|
+
## Jira
|
|
84
|
+
|
|
85
|
+
Jira is reached through `mcp-atlassian`. A script cannot call MCP, so the agent is the transport and the scripts do
|
|
86
|
+
the mapping:
|
|
87
|
+
|
|
88
|
+
1. **Plan the push.**
|
|
89
|
+
```bash
|
|
90
|
+
node .claude/skills/backlog/scripts/jira-plan.ts [ST-NNN ...]
|
|
91
|
+
```
|
|
92
|
+
**Pull before you push.** `jira-plan` cannot see Jira, so it cannot detect a conflict; the import does. Fetch and
|
|
93
|
+
import the issues first, then plan. A local file that the import left alone is safe to push; a conflict means the
|
|
94
|
+
tracker won and the mirror is current.
|
|
95
|
+
It prints JSON actions: `create` (`jira_create_issue`), `update` (`jira_update_issue`), `transition`
|
|
96
|
+
(`jira_transition_issue`) and `link` (`jira_create_issue_link`). For a story with no `external.key` the action is
|
|
97
|
+
`create`; its links are planned after the key exists.
|
|
98
|
+
2. **Execute with the MCP server named in `backlog.jira.mcpServer`.**
|
|
99
|
+
- `create`: call `jira_create_issue` with the given `arguments`. Keep `key` and `browse_url` from the response.
|
|
100
|
+
- `update`: call `jira_update_issue`.
|
|
101
|
+
- `transition`: call `jira_get_transitions` for the issue first, pick the transition that reaches the `target`
|
|
102
|
+
status, then call `jira_transition_issue`. If the issue is already there, skip it.
|
|
103
|
+
- `link`: call `jira_create_issue_link`. `Blocks` is read as "outward issue blocks inward issue", so a story that
|
|
104
|
+
depends on another has the dependency as `outward_issue_key`.
|
|
105
|
+
- An action with `skip` is a note, not an MCP call: either the story is linked to another provider (fix the
|
|
106
|
+
configuration or the external ref), or a link has to wait until the dependency has a Jira key. Re-run the plan
|
|
107
|
+
after recording the missing key.
|
|
108
|
+
3. **Record the keys.**
|
|
109
|
+
```bash
|
|
110
|
+
node .claude/skills/backlog/scripts/jira-record.ts --from results.json
|
|
111
|
+
# [{"story":"ST-012","key":"PROJ-123","url":"https://…/browse/PROJ-123"}]
|
|
112
|
+
```
|
|
113
|
+
4. **Create the links.** Run `jira-plan.ts` again now that the keys exist and execute the `link` actions.
|
|
114
|
+
5. **Pull.**
|
|
115
|
+
- Search with JQL `project = "<project>" AND labels = "kataster" ORDER BY key ASC`, or load the known keys
|
|
116
|
+
individually with `jira_get_issue`; ask for `summary, description, status, labels, priority`.
|
|
117
|
+
- Write the result as JSON and import it:
|
|
118
|
+
```bash
|
|
119
|
+
node .claude/skills/backlog/scripts/backlog-pull.ts --from issues.json
|
|
120
|
+
```
|
|
121
|
+
Accepted shape:
|
|
122
|
+
```json
|
|
123
|
+
[{ "key": "PROJ-123", "summary": "ST-012: …", "description": "…", "status": "Ready",
|
|
124
|
+
"labels": ["kataster", "kataster-status-ready"], "priority": "High",
|
|
125
|
+
"browse_url": "https://…/browse/PROJ-123" }]
|
|
126
|
+
```
|
|
127
|
+
`{ "key": "…", "fields": { "summary": "…", "description": "…", "status": { "name": "…" },
|
|
128
|
+
"priority": { "name": "…" }, "labels": [] } }` is accepted too.
|
|
129
|
+
|
|
130
|
+
Jira may normalize Markdown on the way in and out. The mirror follows what Jira returns, so the first pull can
|
|
131
|
+
reformat a body; that is not a lost change. The machine block lives in a code block because Jira Cloud ADF drops HTML
|
|
132
|
+
comments.
|
|
133
|
+
|
|
134
|
+
`statusMap` and `priorityMap` must match the project's Jira workflow and priority scheme. If a status has no
|
|
135
|
+
transition, report it – do not invent one. The status label (`kataster-status-*`) is a fallback when the Jira status
|
|
136
|
+
name is not in the map. Jira labels allow only letters, numbers and a few separators, so story labels are sanitized on
|
|
137
|
+
the issue; the machine block keeps the original labels and restores them on a pull.
|
|
138
|
+
|
|
139
|
+
## Conflicts
|
|
140
|
+
|
|
141
|
+
The sync keeps a hash of the local file and of the remote issue at the last successful sync
|
|
142
|
+
(`.claude/state/backlog-sync.json`):
|
|
143
|
+
|
|
144
|
+
- **Remote changed, local unchanged** → pull updates the mirror.
|
|
145
|
+
- **Remote unchanged, local changed** → pull leaves the local story alone; push publishes it.
|
|
146
|
+
- **Both changed** → *the tracker wins*. Pull overwrites the mirror, backs the local file up to
|
|
147
|
+
`.kataster/backup/backlog/<timestamp>/` and says so. Push refuses that story and tells you to pull first.
|
|
148
|
+
- **A local story has no external ref** → push creates it; pull reports it as local-only.
|
|
149
|
+
- **A linked issue disappeared** → reported as orphan. Nothing is deleted automatically.
|
|
150
|
+
- **Jira has no conflict detection in the plan** → pull first. The import compares the hashes and reports a conflict;
|
|
151
|
+
only then is the mirror safe to push.
|
|
152
|
+
|
|
153
|
+
Never force a conflict away, never delete a story because its issue is gone, and never re-create an orphaned issue
|
|
154
|
+
silently – ask the user.
|
|
155
|
+
|
|
156
|
+
## When to sync
|
|
157
|
+
|
|
158
|
+
- after `requirements-engineer` created or changed stories → `push`
|
|
159
|
+
- after `product-owner` or `lead-dev` changed `priority`, `size`, `risk`, `depends_on` → `push`
|
|
160
|
+
- before `/review-stories` → `pull`, then review, then `push` the approved stories
|
|
161
|
+
- before `/implement` → `pull`; after each status change the workflow updates the tracker (GitHub directly, Jira by
|
|
162
|
+
the printed MCP transition), and after `done` → `push`
|
|
163
|
+
- before `/groom-backlog` → `pull`; after applying the grooming → `push`
|
|
164
|
+
- a story must be linked before it reaches `ready` – the validator refuses an unlinked `ready` story in tracker mode
|
|
165
|
+
|
|
166
|
+
## Setup
|
|
167
|
+
|
|
168
|
+
**GitHub:** `backlog.provider: "github"`. Fill `backlog.github.repo` if it is not the current repository. Authenticate
|
|
169
|
+
`gh` once.
|
|
170
|
+
|
|
171
|
+
**Jira:** `backlog.provider: "jira"`, a real `backlog.jira.project`, and the `mcp-atlassian` server configured in the
|
|
172
|
+
harness. Claude Code: `.mcp.json`; OpenCode: the `mcp` block in `opencode.jsonc`:
|
|
173
|
+
|
|
174
|
+
```jsonc
|
|
175
|
+
{
|
|
176
|
+
"mcp": {
|
|
177
|
+
"mcp-atlassian": {
|
|
178
|
+
"type": "local",
|
|
179
|
+
"command": ["uvx", "mcp-atlassian"],
|
|
180
|
+
"enabled": true,
|
|
181
|
+
"environment": { "JIRA_URL": "https://…", "JIRA_USERNAME": "…", "JIRA_API_TOKEN": "…" }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Never put tokens in `.claude/project.json` or in a committed file. `/backlog-sync` reports a missing server, an empty
|
|
188
|
+
project or a missing `gh` login – tell the user, don't work around it.
|
|
189
|
+
|
|
190
|
+
## Rules
|
|
191
|
+
|
|
192
|
+
- `docs/stories/` is the mirror in tracker mode, not the source. Changes made there are pushed; changes made in the
|
|
193
|
+
tracker are pulled.
|
|
194
|
+
- The tracker leads on conflict; the local file is backed up, never silently lost.
|
|
195
|
+
- Story labels must not start with the management prefix (`kataster` / `kataster-…`); those are owned by the sync.
|
|
196
|
+
- `ST-NNN` is stable. Do not renumber, do not change `external` by hand, do not edit `BACKLOG.md`.
|
|
197
|
+
- Reviews and open questions are local artifacts – do not push them.
|
|
198
|
+
- Report missing access (no `gh`, no MCP server, no transition) and let the user decide.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulls tracker changes into the local mirror.
|
|
3
|
+
*
|
|
4
|
+
* GitHub deterministic, via `gh`
|
|
5
|
+
* Jira requires the issue data the agent fetched through MCP: `--from <issues.json>`
|
|
6
|
+
*
|
|
7
|
+
* Usage: node backlog-pull.ts [--from <jira-issues.json>]
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { loadBacklogConfig } from "../../../lib/config.ts";
|
|
11
|
+
import { pullGithub } from "../../../lib/backlog-github.ts";
|
|
12
|
+
import { jiraImport } from "../../../lib/backlog-jira.ts";
|
|
13
|
+
|
|
14
|
+
const cfg = loadBacklogConfig();
|
|
15
|
+
const fromIndex = process.argv.indexOf("--from");
|
|
16
|
+
const from = fromIndex >= 0 ? process.argv[fromIndex + 1] : "";
|
|
17
|
+
|
|
18
|
+
if (cfg.provider === "local") {
|
|
19
|
+
console.log("Backlog: local – docs/stories/ is the backlog, nothing to pull.");
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
if (cfg.provider === "jira" && !from) {
|
|
23
|
+
console.error(
|
|
24
|
+
"Backlog: jira is reached through mcp-atlassian, which only the agent can call.\n" +
|
|
25
|
+
" Fetch the issues with jira_search/jira_get_issue, write them as JSON, then:\n" +
|
|
26
|
+
" node .claude/skills/backlog/scripts/backlog-pull.ts --from <issues.json>",
|
|
27
|
+
);
|
|
28
|
+
process.exit(2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (cfg.provider === "github") {
|
|
33
|
+
const report = pullGithub(cfg);
|
|
34
|
+
for (const line of report.lines) console.log(line);
|
|
35
|
+
} else {
|
|
36
|
+
const raw = JSON.parse(readFileSync(from, "utf8"));
|
|
37
|
+
const issues = Array.isArray(raw) ? raw : raw.issues ?? raw.values ?? raw.results ?? [];
|
|
38
|
+
const report = jiraImport(cfg, issues);
|
|
39
|
+
for (const line of report.lines) console.log(line);
|
|
40
|
+
}
|
|
41
|
+
process.exit(0);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publishes local stories to the tracker.
|
|
3
|
+
*
|
|
4
|
+
* GitHub deterministic, via `gh`
|
|
5
|
+
* Jira cannot be done from a script – MCP is agent-only. This prints the exact next step and exits 2.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node backlog-push.ts [ST-NNN ...]
|
|
8
|
+
*/
|
|
9
|
+
import { loadBacklogConfig } from "../../../lib/config.ts";
|
|
10
|
+
import { pushGithub } from "../../../lib/backlog-github.ts";
|
|
11
|
+
|
|
12
|
+
const ids = process.argv.slice(2).filter((arg) => /^ST-\d+$/i.test(arg));
|
|
13
|
+
const cfg = loadBacklogConfig();
|
|
14
|
+
|
|
15
|
+
if (cfg.provider === "local") {
|
|
16
|
+
console.log("Backlog: local – the story files are the backlog, nothing to push.");
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
if (cfg.provider === "jira") {
|
|
20
|
+
const scope = ids.length ? ` ${ids.join(" ")}` : "";
|
|
21
|
+
console.error(
|
|
22
|
+
"Backlog: jira is reached through mcp-atlassian, which only the agent can call.\n" +
|
|
23
|
+
` 1. node .claude/skills/backlog/scripts/jira-plan.ts${scope}\n` +
|
|
24
|
+
" 2. Execute every action with the mcp-atlassian tools.\n" +
|
|
25
|
+
" 3. node .claude/skills/backlog/scripts/jira-record.ts --from <results.json>\n" +
|
|
26
|
+
" 4. Run jira-plan.ts again to create the issue links.",
|
|
27
|
+
);
|
|
28
|
+
process.exit(2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const report = pushGithub(cfg, ids);
|
|
33
|
+
for (const line of report.lines) console.log(line);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|