@gcunharodrigues/wrxn 0.21.1 → 0.22.0

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/manifest.json CHANGED
@@ -558,6 +558,11 @@
558
558
  "class": "managed",
559
559
  "profile": "project"
560
560
  },
561
+ {
562
+ "path": ".wrxn/tracker-target.cjs",
563
+ "class": "managed",
564
+ "profile": "project"
565
+ },
561
566
  {
562
567
  "path": ".wrxn/wiki.cjs",
563
568
  "class": "managed",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gcunharodrigues/wrxn",
3
- "version": "0.21.1",
3
+ "version": "0.22.0",
4
4
  "description": "WRXN Kernel — installable AI operating system. Two profiles (project | workspace), pull-based updates, managed/seeded/state file classes.",
5
5
  "bin": {
6
6
  "wrxn": "bin/wrxn.cjs"
@@ -83,3 +83,34 @@ Or "None - can start immediately" if no blockers.
83
83
  </issue-template>
84
84
 
85
85
  Do NOT close or modify any parent issue.
86
+
87
+ ## `--repo` / cross-repo targeting
88
+
89
+ By default this skill publishes the slices to the project's configured tracker (see
90
+ `docs/agents/issue-tracker.md`). Passing **`--repo owner/repo`** instead targets a named **GitHub** repo
91
+ for this one invocation (e.g. the kernel or `recon-wrxn`), so you can slice a sibling repo from this
92
+ session without leaving the pipeline. Resolve the target through the ONE shared helper —
93
+ `.wrxn/tracker-target.cjs` — never hand-roll the parsing:
94
+
95
+ ```bash
96
+ # WITH --repo present: pass the owner/repo value as the trailing argument.
97
+ node -e 'console.log(JSON.stringify(require("./.wrxn/tracker-target.cjs").resolveTarget(process.argv[1])))' "owner/repo"
98
+ # WITHOUT --repo: OMIT the argument entirely (process.argv[1] is undefined → resolves to local).
99
+ # NEVER pass "" — resolveTarget("") THROWS (empty is malformed, not "absent"), which would break the local path.
100
+ node -e 'console.log(JSON.stringify(require("./.wrxn/tracker-target.cjs").resolveTarget(process.argv[1])))'
101
+ ```
102
+
103
+ It returns `{ mechanism, repo, ghBaseArgs }` and **throws loud** on a malformed / empty / trailing
104
+ `--repo` BEFORE any publish — let that refusal surface; never proceed on a bad target.
105
+
106
+ - **`mechanism: "local"`** (no `--repo`) → publish exactly as today, to `.scratch/` — unchanged.
107
+ - **`mechanism: "github"`** → publish via `gh`, prepending the returned **`ghBaseArgs`** (`-R owner/repo`)
108
+ to every `gh` call, e.g. `gh issue create -R owner/repo --title "…" --body "…" --label ready-for-agent`.
109
+ Slices are AFK-ready, so apply `ready-for-agent` from the shared wrxn triage vocab
110
+ (`ready-for-agent` / `backlog` / `epic`). `gh` fails loud if the target lacks the label; do not work
111
+ around it. The helper's `publishIssue({ target, title, body, label }, gh)` builds that exact argv and
112
+ refuses an off-vocab label — use it as the create boundary.
113
+
114
+ **Target the SAME `owner/repo` the PRD was published to** (via `to-prd --repo`), so each slice's "Parent"
115
+ references a real PRD issue number on that tracker. Publish slices in dependency order so the "Blocked by"
116
+ field can cite real issue identifiers.
@@ -72,3 +72,34 @@ A description of the things that are out of scope for this PRD.
72
72
  Any further notes about the feature.
73
73
 
74
74
  </prd-template>
75
+
76
+ ## `--repo` / cross-repo targeting
77
+
78
+ By default this skill publishes the PRD to the project's configured tracker (see
79
+ `docs/agents/issue-tracker.md`). Passing **`--repo owner/repo`** instead targets a named **GitHub** repo
80
+ for this one invocation (e.g. the kernel or `recon-wrxn`), so you can spec a sibling repo from this
81
+ session without leaving the pipeline. Resolve the target through the ONE shared helper —
82
+ `.wrxn/tracker-target.cjs` — never hand-roll the parsing:
83
+
84
+ ```bash
85
+ # WITH --repo present: pass the owner/repo value as the trailing argument.
86
+ node -e 'console.log(JSON.stringify(require("./.wrxn/tracker-target.cjs").resolveTarget(process.argv[1])))' "owner/repo"
87
+ # WITHOUT --repo: OMIT the argument entirely (process.argv[1] is undefined → resolves to local).
88
+ # NEVER pass "" — resolveTarget("") THROWS (empty is malformed, not "absent"), which would break the local path.
89
+ node -e 'console.log(JSON.stringify(require("./.wrxn/tracker-target.cjs").resolveTarget(process.argv[1])))'
90
+ ```
91
+
92
+ It returns `{ mechanism, repo, ghBaseArgs }` and **throws loud** on a malformed / empty / trailing
93
+ `--repo` BEFORE any publish — let that refusal surface; never proceed on a bad target.
94
+
95
+ - **`mechanism: "local"`** (no `--repo`) → publish exactly as today, to `.scratch/` — unchanged.
96
+ - **`mechanism: "github"`** → publish via `gh`, prepending the returned **`ghBaseArgs`** (`-R owner/repo`)
97
+ to every `gh` call, e.g. `gh issue create -R owner/repo --title "…" --body "…" --label backlog`. Apply a
98
+ label from the shared wrxn triage vocab (`ready-for-agent` / `backlog` / `epic`) — a PRD is the parent
99
+ epic, so use `backlog` (or `epic`), not `ready-for-agent`. `gh` fails loud if the target lacks the
100
+ label; do not work around it. The helper's `publishIssue({ target, title, body, label }, gh)` builds
101
+ that exact argv and refuses an off-vocab label — use it as the create boundary.
102
+
103
+ **Remember the published PRD's issue number.** When you later run `to-issues --repo`, point it at the
104
+ **SAME** `owner/repo` so each slice's "Parent" is a real issue number on that tracker (not a dangling
105
+ reference).
@@ -102,3 +102,31 @@ Capture everything resolved during grilling under "established so far" so the wo
102
102
  ## Resuming a previous session
103
103
 
104
104
  If prior triage notes exist on the issue, read them, check whether the reporter has answered any outstanding questions, and present an updated picture before continuing. Don't re-ask resolved questions.
105
+
106
+ ## `--repo` / cross-repo targeting
107
+
108
+ By default this skill triages issues on the project's configured tracker (see
109
+ `docs/agents/issue-tracker.md`). Passing **`--repo owner/repo`** instead targets a named **GitHub** repo
110
+ for this one invocation (e.g. the kernel or `recon-wrxn`), so you can manage a sibling repo's backlog
111
+ from this session without leaving the pipeline. Resolve the target through the ONE shared helper —
112
+ `.wrxn/tracker-target.cjs` — never hand-roll the parsing:
113
+
114
+ ```bash
115
+ # WITH --repo present: pass the owner/repo value as the trailing argument.
116
+ node -e 'console.log(JSON.stringify(require("./.wrxn/tracker-target.cjs").resolveTarget(process.argv[1])))' "owner/repo"
117
+ # WITHOUT --repo: OMIT the argument entirely (process.argv[1] is undefined → resolves to local).
118
+ # NEVER pass "" — resolveTarget("") THROWS (empty is malformed, not "absent"), which would break the local path.
119
+ node -e 'console.log(JSON.stringify(require("./.wrxn/tracker-target.cjs").resolveTarget(process.argv[1])))'
120
+ ```
121
+
122
+ It returns `{ mechanism, repo, ghBaseArgs }` and **throws loud** on a malformed / empty / trailing
123
+ `--repo` BEFORE any side-effect — let that refusal surface; never proceed on a bad target.
124
+
125
+ - **`mechanism: "local"`** (no `--repo`) → triage exactly as today, editing the `Status:` line in
126
+ `.scratch/` — unchanged.
127
+ - **`mechanism: "github"`** → manage via `gh`, prepending the returned **`ghBaseArgs`** (`-R owner/repo`)
128
+ to every `gh` call: relabel with `gh issue edit -R owner/repo --add-label … --remove-label …`, close
129
+ with `gh issue close -R owner/repo …`, comment with `gh issue comment -R owner/repo …`, and create with
130
+ the helper's `publishIssue({ target, title, body, label }, gh)`. Apply only labels from the shared wrxn
131
+ triage vocab (`ready-for-agent` / `backlog` / `epic`); `gh` fails loud if the target lacks a label — do
132
+ not work around it (no silent mis-label).
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ // WRXN cross-repo tracker targeting — the ONE shared resolver behind `to-prd --repo`,
4
+ // `to-issues --repo`, and `triage --repo` (PRD #114 / issue #115). It closes the cross-repo seam ADR
5
+ // 0009 only documented (refs #81 AC#4): from a workspace-install session, `--repo owner/repo` lets the
6
+ // HITL skills spec / slice / triage a sibling GitHub repo (the kernel, recon-wrxn) onto that repo's
7
+ // GitHub tracker — without leaving the four-phase pipeline or hand-filing past the adherence guard.
8
+ //
9
+ // Self-contained: this ships INTO an install and MUST NOT import the kernel lib (node stdlib only).
10
+ // The seam mirrors lib/release-cut.cjs: the decision is a PURE core (resolveTarget — parse / validate /
11
+ // select-mechanism), and the gh create/label/close side-effects run through an INJECTED `gh` boundary,
12
+ // so the decision + emitted spec are unit-testable with NO live gh.
13
+
14
+ // owner/repo: a GitHub-legal ALLOWLIST, not a slash/space blocklist — this is the security chokepoint.
15
+ // The skill wirings steer the agent to hand-compose `gh -R owner/repo` Bash, so the validator is the one
16
+ // place that must reject anything that could change the meaning of that command. Allow ONLY what GitHub
17
+ // itself permits: owner = alphanumerics + hyphens but NOT a leading hyphen (a leading `-` would let `gh`
18
+ // read the whole value as a flag — argument injection); repo = alphanumerics + `.` `_` `-`. This refuses
19
+ // every shell metacharacter (`;` `$()` backtick `|` `&` `>`), embedded whitespace, a trailing slash, an
20
+ // extra path segment, and an empty value by construction — all must refuse loud BEFORE any publish.
21
+ // (Reviewer/security convergent finding — FIX 1.)
22
+ const OWNER_REPO = /^[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9._-]+$/;
23
+
24
+ // The shared wrxn triage label vocab — the states already carried on the kernel and recon-wrxn. A label
25
+ // means the same thing across repos; there is NO per-target label config (YAGNI for GitHub siblings).
26
+ // gh fails loud if a target lacks one of these (free remote validation) — publishIssue never swallows it.
27
+ const TRIAGE_LABELS = ['ready-for-agent', 'backlog', 'epic'];
28
+
29
+ /**
30
+ * Resolve the tracker target for an invocation. PURE — the whole cross-repo decision, no side effect.
31
+ * - `--repo` ABSENT (rawRepo nullish) → the install's configured default tracker (today's local-md
32
+ * `.scratch/` path, unchanged). `defaultConfig` IS that default descriptor; the kernel default is
33
+ * local-md (reconfiguring the default is out of scope — the override is per-invocation).
34
+ * - `--repo owner/repo` (valid) → `{ mechanism:'github', repo, ghBaseArgs:['-R', repo] }`. GitHub by
35
+ * construction; NO git-remote inference — tracker TYPE is a config choice, not derivable from a remote
36
+ * (the install proves it: GitHub remote, local-md tracker).
37
+ * - `--repo` PRESENT but malformed / empty / whitespace / trailing → THROW a loud, user-facing Error
38
+ * BEFORE any side-effect, so a bad invocation never half-files (US-5).
39
+ * @param {string|undefined|null} rawRepo the raw `--repo` value (absent ⇒ nullish)
40
+ * @param {{mechanism:string}} [defaultConfig] the install's default tracker descriptor (local-md today)
41
+ * @returns {{mechanism:'local'}|{mechanism:'github',repo:string,ghBaseArgs:string[]}}
42
+ */
43
+ function resolveTarget(rawRepo, defaultConfig = { mechanism: 'local' }) {
44
+ if (rawRepo == null) return defaultConfig; // --repo omitted → the configured default tracker
45
+ if (typeof rawRepo !== 'string' || !OWNER_REPO.test(rawRepo)) {
46
+ throw new Error(
47
+ `--repo must be "owner/repo" (got ${JSON.stringify(rawRepo)}) — e.g. gcunharodrigues/wrxn-kernel`,
48
+ );
49
+ }
50
+ return { mechanism: 'github', repo: rawRepo, ghBaseArgs: ['-R', rawRepo] };
51
+ }
52
+
53
+ /**
54
+ * Publish an issue onto a resolved GitHub target, applying ONE label drawn from the shared wrxn vocab.
55
+ * The `gh` runner is an INJECTED boundary (it executes the built argv) — mirroring release-cut's injected
56
+ * `deps`, so this is unit-testable with no live gh and the emitted spec is asserted over a fake. Guards
57
+ * refuse LOUD before the boundary (a non-github target, or an off-vocab label → no silent mis-label); a
58
+ * gh failure (e.g. the target repo lacks the label) is NOT swallowed — it propagates so a mis-labeled or
59
+ * unfiled issue never lands silently (US-6, US-7).
60
+ * @param {{target:object,title:string,body:string,label:string}} spec
61
+ * @param {(argv:string[])=>any} gh the injected gh runner (the real impl shells `gh` with these args)
62
+ * @returns whatever the gh runner returns
63
+ */
64
+ function publishIssue({ target, title, body, label } = {}, gh) {
65
+ if (!target || target.mechanism !== 'github') {
66
+ throw new Error(`publishIssue targets a github tracker (got mechanism "${target && target.mechanism}")`);
67
+ }
68
+ if (!TRIAGE_LABELS.includes(label)) {
69
+ throw new Error(`label "${label}" is off the shared wrxn vocab — expected one of ${TRIAGE_LABELS.join(' | ')}`);
70
+ }
71
+ if (typeof gh !== 'function') throw new Error('publishIssue requires an injected gh runner');
72
+ const argv = ['issue', 'create', ...target.ghBaseArgs, '--title', title, '--body', body, '--label', label];
73
+ return gh(argv); // the side-effect lives at the boundary; a gh failure propagates loud (no swallow)
74
+ }
75
+
76
+ module.exports = { resolveTarget, publishIssue, TRIAGE_LABELS, OWNER_REPO };
@@ -23,3 +23,23 @@ Read the file at the referenced path. The user will normally pass the path or th
23
23
 
24
24
  To use GitHub or GitLab issues instead, re-run `setup-matt-pocock-skills` and pick the
25
25
  matching tracker — it will rewrite this file from the right template.
26
+
27
+ ## Per-invocation override: `--repo owner/repo` (cross-repo targeting)
28
+
29
+ `to-prd`, `to-issues`, and `triage` accept an optional `--repo owner/repo` flag that targets a
30
+ named **GitHub** repo for that one invocation, instead of this install's default tracker above. It
31
+ exists so the operator can spec / slice / triage a sibling GitHub repo (e.g. the kernel or
32
+ `recon-wrxn`) end-to-end from a workspace-install session, without leaving the four-phase pipeline.
33
+
34
+ - **Absent `--repo`** → the default tracker described above (local-markdown `.scratch/`), unchanged.
35
+ Existing install-local workflows are untouched.
36
+ - **`--repo owner/repo`** → publish/manage on that GitHub repo via `gh`, using the shared wrxn triage
37
+ vocab (`ready-for-agent` / `backlog` / `epic`). The repo is passed explicitly to `gh -R owner/repo`
38
+ — it is **not** inferred from a git remote (the tracker TYPE is a config choice, not the remote).
39
+ - **Malformed / empty / trailing `--repo`** (anything not exactly `owner/repo`) → the skill refuses
40
+ loud *before* any publish, so a bad invocation never half-files.
41
+
42
+ The override is per-invocation only; it never reconfigures this install's default tracker. All three
43
+ skills resolve the target through the one shared `.wrxn/tracker-target.cjs` helper (see those skills'
44
+ "`--repo` / cross-repo targeting" sections), so validation, label handling, and `gh`-arg construction
45
+ are identical across them.