@raquezha/norpiv 0.0.7 → 0.0.8

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 CHANGED
@@ -12,6 +12,7 @@ The RPIV engine splits task execution into separate, focused phases:
12
12
 
13
13
  | Phase | Command | Purpose | Input / Output Files |
14
14
  | :--- | :--- | :--- | :--- |
15
+ | **0. Refine (optional)** | `/refine [source]:[id]` | Repair tracker readiness before RPIV when outcome, acceptance criteria, ownership, dependencies, or decomposition are still unclear. | Proposes tracker edits/child work with ownership/dependency recommendations; no `.workflow/` state |
15
16
  | **1. Ingest** | `/triage [source]:[id]` | Initial task verification and workspace setup. | Creates `.workflow/tasks/[source-id]/WORK.md` & `metadata.json` |
16
17
  | **2. Scoping** | `/frame` | Author a clear, structured task brief. | Populates `WORK.md` ➔ `[BRIEF]` section |
17
18
  | **3. Interrogate**| `/grill-with-docs` | Stress-test brief against rules and docs. | Records decisions in `WORK.md` ➔ `[GRILL]` |
@@ -46,6 +47,10 @@ Rules:
46
47
  - **Measure Twice, Cut Once**: Never implement code during scoping or planning. The agent will wait for an explicit `EXECUTE` statement before modifying files.
47
48
  - **One Source of Truth**: All task state belongs in `.workflow/tasks/[source-id]/WORK.md`. Avoid creating separate `PROBLEM.md` or `PLAN.md` files.
48
49
  - **Safe Branching**: Triage and planning happen on the main branch. Create the feature branch (`feat/*` or `fix/*`) only when starting `/implement`.
50
+ - **Real In-Progress Starts at Implement**: `/refine`, `/triage`, `/frame`, and `/plan` are preparation, not execution. Move the remote Jira/GitHub/GitLab work item to an in-progress state only when `/implement` actually begins.
51
+ - **Refine Preserves Intent**: `/refine` should preserve product-authored text, append acceptance criteria instead of rewriting it, and only assign or link child work when ownership/dependency signals are clear.
52
+ - **Canonical Pointer**: `.workflow/active.json` is the active RPIV pointer. Legacy `active_task.json` is compatibility-only during migration.
53
+ - **Normalized Intake**: `/triage` writes a small local projection into `WORK.md`; tracker snapshots live in `metadata.json`, not raw CLI dumps.
49
54
 
50
55
  ## 📦 Install as a skill bundle
51
56
 
@@ -55,7 +60,7 @@ Best for trying or handing off RPIV skills without installing the full `nothing`
55
60
 
56
61
  ```bash
57
62
  npx -y skills add raquezha/nothing --full-depth -g -a pi \
58
- -s triage frame grill-with-docs plan implement verify sync post-merge-prune update-docs distill \
63
+ -s refine triage frame grill-with-docs plan implement verify sync post-merge-prune update-docs distill \
59
64
  -y
60
65
  ```
61
66
 
@@ -88,7 +93,7 @@ Targets:
88
93
 
89
94
  | Hat | Purpose |
90
95
  | :--- | :--- |
91
- | `pi --rpiv` | Full RPIV workflow. Loads triage, frame, grill-with-docs, plan, implement, verify, sync, post-merge-prune, and update-docs. |
96
+ | `pi --rpiv` | Full RPIV workflow. Loads refine, triage, frame, grill-with-docs, plan, implement, verify, sync, post-merge-prune, and update-docs. |
92
97
  | `pi --notes` | Conversation distiller. Saves useful thinking to Obsidian without RPIV ceremony. |
93
98
 
94
99
  ## 📝 Pre-RPIV note capture
@@ -127,34 +132,39 @@ Research is a separate local workflow bundle in `packages/workflows/noresearch`
127
132
 
128
133
  If installed via `npx skills add` or `norpiv-install`, invoke the skills directly in your agent instead.
129
134
 
130
- 2. **Triage an Issue**:
135
+ 2. **Optional Tracker Refinement**:
136
+ ```text
137
+ /refine github:45
138
+ ```
139
+
140
+ 3. **Triage an Issue**:
131
141
  ```text
132
142
  /triage github:45
133
143
  ```
134
144
 
135
- 3. **Frame the Work**:
145
+ 4. **Frame the Work**:
136
146
  ```text
137
147
  /frame
138
148
  ```
139
149
 
140
- 4. **Verify Constraints**:
150
+ 5. **Verify Constraints**:
141
151
  ```text
142
152
  /grill-with-docs
143
153
  ```
144
154
 
145
- 5. **Write the Plan Slices**:
155
+ 6. **Write the Plan Slices**:
146
156
  ```text
147
157
  /plan
148
158
  ```
149
159
 
150
- 6. **Authorize Execution**:
160
+ 7. **Authorize Execution**:
151
161
  Provide the agent explicit permission to implement:
152
162
  ```text
153
163
  EXECUTE
154
164
  /implement
155
165
  ```
156
166
 
157
- 7. **Verify & Close**:
167
+ 8. **Verify & Close**:
158
168
  ```text
159
169
  /verify
160
170
  /sync
@@ -167,39 +177,24 @@ The bundle includes helper scripts used by the workflow skills:
167
177
 
168
178
  - `scripts/triage_helper.sh`
169
179
  - `scripts/validate_active_task.sh`
170
- - `scripts/reposcry-bootstrap.sh`
171
- - `scripts/reposcry-task-context.sh`
172
- - `scripts/reposcry-refresh.sh`
180
+ - `scripts/graphify-grill.sh`
181
+ - `scripts/graphify-grill.py`
173
182
 
174
183
  When skills are loaded directly from this package, relative references like `../scripts/...` resolve against the package root. When skills are installed with `norpiv-install`, the same layout is recreated under the target runtime.
175
184
 
176
- ## 🔎 Optional RepoScry integration
185
+ ## 🔎 Optional Graphify grilling
177
186
 
178
- RepoScry is an optional repo-memory layer for RPIV. norpiv does **not** require it.
187
+ Graphify is an optional grill-only evidence layer for RPIV. norpiv does **not** require it.
179
188
 
180
- If `reposcry` is installed:
189
+ If bootstrap provisions `~/.graphify/venv`, `/grill-with-docs` may run:
181
190
 
182
191
  ```bash
183
- ./scripts/reposcry-bootstrap.sh
184
- ./scripts/reposcry-task-context.sh "fix dependency graph rebuild"
185
- # edit code
186
- ./scripts/reposcry-refresh.sh main
187
- reposcry validate main HEAD
192
+ ./scripts/graphify-grill.sh
188
193
  ```
189
194
 
190
- Typical usage by phase:
191
-
192
- - `/triage`: optionally seed `.reposcry/` with `scripts/reposcry-bootstrap.sh`
193
- - `/frame`: optionally generate `.reposcry/AI_CONTEXT.md` with `scripts/reposcry-task-context.sh`
194
- - `/grill-with-docs`: optionally use `reposcry query_graph`, `get_architecture_overview`, and `get_impact_radius`
195
- - `/implement`: optionally run `scripts/reposcry-refresh.sh` after edit batches
196
- - `/verify`: optionally add `reposcry validate main HEAD` and affected-flow output as extra evidence
197
-
198
- If RepoScry is absent, the helpers no-op and RPIV continues with normal repo reading, grep, and tests.
199
-
200
- RepoScry guardrails:
195
+ Guardrails:
201
196
 
202
- - `.reposcry/` is generated local cache and must not be committed.
203
- - `scripts/reposcry-bootstrap.sh` automatically adds `.reposcry/` to the project `.gitignore` before initializing RepoScry.
204
- - If `.reposcry/` is already tracked or staged, bootstrap stops and tells you to remove it from the index.
205
- - `.reposcryignore` is indexing policy, not cache. Review and commit it when you want stable RepoScry behavior across machines.
197
+ - input is a temporary `git archive HEAD` extraction, not the live repository root
198
+ - extraction is structural only by default
199
+ - Graphify warnings never block grilling; normal source reading remains the fallback
200
+ - `INFERRED` or `AMBIGUOUS` edges are leads that still require source verification
package/frame/SKILL.md CHANGED
@@ -10,15 +10,14 @@ description: Define the task brief inside the active WORK.md. Use after /triage
10
10
  Turn raw task context into the stable "what/why" brief.
11
11
 
12
12
  ## Guardrails
13
- - READ: `.workflow/active.json` / `.workflow/active_task.json`, active `WORK.md`, and `.reposcry/AI_CONTEXT.md` when present.
14
- - WRITE: `WORK.md` -> `[BRIEF]` section and append to `[LOG]` only; optional `.reposcry/AI_CONTEXT.md` when RepoScry is installed.
13
+ - READ: `.workflow/active.json` first, then legacy compatibility `.workflow/active_task.json` only if needed, and active `WORK.md`.
14
+ - WRITE: `WORK.md` -> `[BRIEF]` section and append to `[LOG]` only.
15
15
  - NEVER: create `PROBLEM.md`, `PRD.md`, or extra planning files.
16
16
  - NEVER: overwrite `[PLAN]` or `[GRILL]`.
17
17
  - NEVER: ask whether to frame if the user invoked `/frame`; do it.
18
18
 
19
19
  ## Workflow
20
20
  1. Read the active task and remote metadata.
21
- 2. If RepoScry is available, run the bundled `../scripts/reposcry-task-context.sh "<task summary>"` helper to generate `.reposcry/AI_CONTEXT.md`, then use that file as supplemental repo context. The helper path must preserve RepoScry guardrails: `.reposcry/` ignored, cache never tracked, `.reposcryignore` treated as reviewable indexing policy. Continue normally when unavailable.
22
21
  3. Determine brief type:
23
22
  - **Problem** for bugs, regressions, crashes, broken behavior.
24
23
  - **Proposal** for features, enhancements, refactors, new behavior.
@@ -10,31 +10,25 @@ description: Stress-test the active WORK.md brief against docs, code, and domain
10
10
  Challenge the brief before planning. This replaces passive ubiquitous-language collection with active clarification.
11
11
 
12
12
  ## Guardrails
13
- - READ: `.workflow/active.json` / `.workflow/active_task.json`, active `WORK.md` `[BRIEF]`, `CONTEXT.md`, relevant `docs/agents/*`, and `.reposcry/AI_CONTEXT.md` when present.
13
+ - READ: `.workflow/active.json` first, then legacy compatibility `.workflow/active_task.json` only if needed, plus active `WORK.md` `[BRIEF]`, `CONTEXT.md`, and relevant `docs/agents/*`.
14
14
  - WRITE: `WORK.md` -> append to `[GRILL]` and `[LOG]` only; durable docs only when a stable rule is confirmed.
15
15
  - NEVER: edit `[BRIEF]` silently; propose brief changes if contradictions are found.
16
16
  - NEVER: plan or implement during grilling.
17
17
  - NEVER: ask questions the codebase can answer; inspect first.
18
18
 
19
19
  ## Workflow
20
- 1. **Context Loading**: Read the active brief, `CONTEXT.md`, relevant `docs/agents/*`, and `.reposcry/AI_CONTEXT.md` when available.
20
+ 1. **Context Loading**: Read the active brief, `CONTEXT.md`, and relevant `docs/agents/*`.
21
21
  2. **Investigation & Trace**:
22
22
  - Locate the files/lines mentioned in the brief.
23
23
  - Trace the data flow related to the problem/proposal.
24
24
  - Search for "Impact Surface": Who else uses or depends on these components?
25
- 3. **Optional RepoScry graph pass**: if `reposcry` is available, use it to ground architecture and blast radius. Before relying on it, ensure the bundled bootstrap/context helper has kept `.reposcry/` ignored and untracked. Use commands such as:
26
- - `reposcry --repo . get_architecture_overview --format json`
27
- - `reposcry --repo . query_graph "callers_of <symbol>"`
28
- - `reposcry --repo . query_graph "tests_for <symbol>"`
29
- - `reposcry --repo . get_impact_radius <symbol> --depth 4`
30
- Proceed normally when RepoScry is absent.
25
+ 3. **Optional Graphify pass**: run `../scripts/graphify-grill.sh` once for bounded structural evidence. It reads only a temporary `git archive HEAD` extraction and warns instead of failing when unavailable. Treat `INFERRED` or `AMBIGUOUS` edges as leads: verify them in source before recording conclusions.
31
26
  4. **Cross-check**: Compare findings against docs, ADRs, and repo patterns.
32
- 5. **Relentless Interview**:
33
- - Ask one question at a time to resolve contradictions or clarify ambiguity.
34
- - Challenge the brief if the code behaves differently than described.
35
- 6. **Log Evidence**: Append resolved decisions, technical findings, edge cases, and constraints to `[GRILL]`.
36
- 7. **Log Activity**: Append a timestamped summary of the grilling session to `[LOG]` (Format: `YYYY-MM-DD hh:mm AM/PM`).
37
- 8. **Context Curation**: If a durable term/rule emerges, propose or apply a concise `docs/agents/*` update.
27
+ 5. **Challenge**: record confirmed constraints, challenged assumptions, chosen decisions, open blockers / HITL questions, and risks handed to `/plan` or `/implement`.
28
+ 6. **Interview**: ask one question at a time only for execution-blocking ambiguity the repository cannot answer.
29
+ 7. **Log Evidence**: Append resolved decisions, technical findings, edge cases, and constraints to `[GRILL]`.
30
+ 8. **Log Activity**: Append a timestamped summary of the grilling session to `[LOG]` (Format: `YYYY-MM-DD hh:mm AM/PM`).
31
+ 9. **Context Curation**: If a durable term/rule emerges, propose or apply a concise `docs/agents/*` update.
38
32
 
39
33
  ## Output contract
40
34
  End with:
@@ -10,34 +10,34 @@ description: Implement the next approved vertical slice from the active WORK.md
10
10
  Execute one functional vertical slice and hand it to the human for review.
11
11
 
12
12
  ## Guardrails
13
- - READ: `.workflow/active.json` / `.workflow/active_task.json` then active `WORK.md` `[PLAN]`, `[BRIEF]`, and relevant `[LOG]` evidence.
14
- - WRITE: code changes and `WORK.md` -> append to `[LOG]` only; optional `.reposcry/` cache refresh when RepoScry is installed.
13
+ - READ: `.workflow/active.json` first, then compatibility `.workflow/active_task.json` only if needed, then active `WORK.md` `[PLAN]`, `[BRIEF]`, and relevant `[LOG]` evidence.
14
+ - WRITE: code changes and `WORK.md` -> append to `[LOG]` only.
15
15
  - NEVER: edit `[BRIEF]` or `[GRILL]`.
16
16
  - NEVER: implement without explicit user instruction.
17
- - NEVER: commit a Jira-tracked task with a Jira-less subject; if the active task source is `jira`, the commit subject MUST include the Jira key from `.workflow/active_task.json` (e.g. `fix(PROJ-123): ...`).
18
- - NEVER: hide the Jira key only in the commit body/footer when the task is Jira-tracked; the subject itself must carry the key.
19
17
  - NEVER: add `Signed-off-by`; only the human can certify DCO.
20
18
  - NEVER: freestyle PR/MR descriptions; use the Draft PR/MR body contract below.
21
19
 
22
20
  ## Workflow
23
21
  1. Identify the first approved unchecked slice in `[PLAN]`.
24
- 2. Move tracked task to **In Progress** only when implementation actually starts.
25
- 3. **Mandatory Branch Check**: You MUST run the branch enforcement script before modifying any code.
22
+ 2. **Mandatory Branch Check**: You MUST run the branch enforcement script before modifying any code.
26
23
  - Use the absolute path if possible: `<skill_location>/scripts/enforce-branch.sh`.
27
24
  - This script prevents accidental implementation on `main`/`master`.
28
25
  - If the script switches branches, you must update the `[META]` section of `WORK.md` to reflect the new branch name.
29
26
  - If the script fails, STOP and ask the human for help. Do not proceed with code changes.
30
- 4. Optional RepoScry blast-radius pass: if `reposcry` is available, inspect impact before wide edits (`reposcry --repo . get_impact_radius <symbol> --depth 4` or related graph queries). Continue normally when RepoScry is absent.
27
+ 3. Select the executable child item, not an umbrella parent; if it is unclear, STOP and ask.
28
+ 4. Move that remote item to **In Progress** immediately before code changes.
29
+ - Jira: transition only when an `In Progress`-style state exists.
30
+ - GitHub / GitLab: use an existing repository-specific status mapping only; do not invent a label, comment, or MR change. If none exists, leave the item unchanged and record why in `[LOG]`.
31
+ - If a configured transition fails, STOP and ask the human; do not begin code changes.
32
+ - Do not mark work in progress during `/refine`, `/triage`, `/frame`, or `/plan`.
31
33
  5. Implement test-first where practical; otherwise document why not in `[LOG]`.
32
- 6. After each edit batch, if the bundled `../scripts/reposcry-refresh.sh` helper is present and `reposcry-update` is installed, run it. RepoScry refresh failure should not block implementation. Never stage or commit `.reposcry/`; it is generated cache. `.reposcryignore` may be committed only after review as indexing policy.
33
- 7. Run the slice verification command and available quality gates.
34
- 8. Commit with a Conventional Commit header and `Assisted-by: [AGENT]:[MODEL] [tools]` footer (populating the agent name and model ID from the current session context).
35
- - For Jira-tracked tasks, the header MUST include the Jira key in the scope position: `fix(PROJ-123): ...` or `feat(PROJ-123): ...`.
36
- - If release-note tooling also needs the key in parsed text, add `Refs: PROJ-123` in the body/footer as well.
37
- 9. Push and open a Draft PR/MR with `gh` or `glab` when a remote exists.
38
- - For Jira-tracked tasks, the PR/MR title MUST also include the Jira key and should mirror the commit subject.
39
- 10. Use a temporary body file (`--body-file` or API equivalent) for PR/MR descriptions to avoid shell quoting and markdown escaping bugs.
40
- 11. Append summary, commit hash, and PR/MR link to `[LOG]` (Format: `YYYY-MM-DD hh:mm AM/PM`).
34
+ 6. Run the slice verification command and available quality gates.
35
+ 7. Commit using the repository's normal conventions and hooks/CI rules.
36
+ - If the repo or tracker expects a Jira key, preserve it where that repository normally requires it.
37
+ - If AI attribution trailers are required, obtain them from the repository's documented policy rather than hard-coding RPIV-specific formatting.
38
+ 8. Push and open a Draft PR/MR with `gh` or `glab` when a remote exists.
39
+ 9. Use a temporary body file (`--body-file` or API equivalent) for PR/MR descriptions to avoid shell quoting and markdown escaping bugs.
40
+ 10. Append summary, commit hash, and PR/MR link to `[LOG]` (Format: `YYYY-MM-DD hh:mm AM/PM`).
41
41
 
42
42
  ## Draft PR/MR body contract
43
43
 
@@ -44,22 +44,32 @@ json_pretty() {
44
44
 
45
45
  # Find repo root to ensure paths are absolute and reliable
46
46
  REPO_ROOT=$(git rev-parse --show-toplevel)
47
+ ACTIVE_WORKFLOW_FILE="$REPO_ROOT/.workflow/active.json"
48
+ # Deprecated compatibility fallback during active.json migration. Remove once all readers stop depending on active_task.json.
47
49
  ACTIVE_TASK_FILE="$REPO_ROOT/.workflow/active_task.json"
48
-
49
- if [[ ! -f "$ACTIVE_TASK_FILE" ]]; then
50
- echo "ERROR: No active task found at $ACTIVE_TASK_FILE."
50
+ POINTER_FILE=""
51
+ POINTER_LABEL=""
52
+
53
+ if [[ -f "$ACTIVE_WORKFLOW_FILE" ]]; then
54
+ POINTER_FILE="$ACTIVE_WORKFLOW_FILE"
55
+ POINTER_LABEL="active.json"
56
+ elif [[ -f "$ACTIVE_TASK_FILE" ]]; then
57
+ POINTER_FILE="$ACTIVE_TASK_FILE"
58
+ POINTER_LABEL="active_task.json"
59
+ else
60
+ echo "ERROR: No active task found at $ACTIVE_WORKFLOW_FILE or $ACTIVE_TASK_FILE."
51
61
  echo "Please run /triage [source]:[id] first."
52
62
  exit 1
53
63
  fi
54
64
 
55
65
  # Read canonical active task fields
56
- TASK_SOURCE=$(json_read "$ACTIVE_TASK_FILE" '.source // empty')
57
- TASK_ID=$(json_read "$ACTIVE_TASK_FILE" '.sourceId // .id // empty')
58
- TASK_PATH=$(json_read "$ACTIVE_TASK_FILE" '.taskPath // .path // empty')
66
+ TASK_SOURCE=$(json_read "$POINTER_FILE" '.source // empty')
67
+ TASK_ID=$(json_read "$POINTER_FILE" '.sourceId // .id // empty')
68
+ TASK_PATH=$(json_read "$POINTER_FILE" '.taskPath // .path // empty')
59
69
 
60
70
  # Prefer explicit taskPath when available (most reliable)
61
71
  if [[ -n "$TASK_PATH" ]]; then
62
- echo "INFO: Using taskPath from active_task.json: $TASK_PATH"
72
+ echo "INFO: Using taskPath from $POINTER_LABEL: $TASK_PATH"
63
73
  if [[ "$TASK_PATH" = /* ]]; then
64
74
  WORK_MD="$TASK_PATH/WORK.md"
65
75
  else
@@ -77,9 +87,9 @@ elif [[ -n "$TASK_ID" ]]; then
77
87
  WORK_MD="$REPO_ROOT/.workflow/tasks/$TASK_FOLDER/WORK.md"
78
88
 
79
89
  else
80
- echo "ERROR: No 'sourceId' or 'taskPath' found in $ACTIVE_TASK_FILE."
81
- echo "Please run /triage [source]:[id] to initialize a task. Here are the file contents for debugging:"
82
- json_pretty "$ACTIVE_TASK_FILE" || true
90
+ echo "ERROR: No 'sourceId' or 'taskPath' found in $POINTER_LABEL."
91
+ echo "Please run /triage [source]:[id] to initialize a task. Here are the file contents for debugging:"
92
+ json_pretty "$POINTER_FILE" || true
83
93
  exit 1
84
94
  fi
85
95
 
@@ -88,8 +98,8 @@ echo "DEBUG: Computed WORK.md path: $WORK_MD"
88
98
 
89
99
  if [[ ! -f "$WORK_MD" ]]; then
90
100
  echo "ERROR: WORK.md not found at $WORK_MD."
91
- echo "Checked active_task.json values:"
92
- json_pretty "$ACTIVE_TASK_FILE" || true
101
+ echo "Checked $POINTER_LABEL values:"
102
+ json_pretty "$POINTER_FILE" || true
93
103
  echo "Please ensure the task was initialized with /triage and that WORK.md exists."
94
104
  exit 1
95
105
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raquezha/norpiv",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Bundled RPIV workflow skills for Pi, Claude, and Codex-style coding agents",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -12,6 +12,7 @@
12
12
  "distill",
13
13
  "frame",
14
14
  "grill-with-docs",
15
+ "refine",
15
16
  "implement",
16
17
  "plan",
17
18
  "scripts",
@@ -36,6 +37,7 @@
36
37
  },
37
38
  "pi": {
38
39
  "skills": [
40
+ "refine",
39
41
  "triage",
40
42
  "frame",
41
43
  "grill-with-docs",
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: refine
3
+ workflow: pre-rpiv
4
+ workflowPhase: refine
5
+ description: "Repair tracker work-item readiness before RPIV intake. Use for jira:, github:, or gitlab: items that still need clarified outcome, acceptance criteria, or child work before /triage."
6
+ ---
7
+
8
+ # Skill: refine
9
+
10
+ Make a tracker item ready for engineering handoff without inventing product decisions.
11
+
12
+ ## Guardrails
13
+ - READ: tracker item, linked/child work, relevant comments, and targeted repo context only when needed for ownership or feasibility.
14
+ - WRITE: proposals in chat first; tracker comments/field edits/child creation only after explicit human approval.
15
+ - NEVER: implement code, create branches, or start RPIV task state.
16
+ - NEVER: invent missing product intent when a human decision is required.
17
+ - NEVER: duplicate existing child work or spam repeated agent comments.
18
+ - NEVER: move work across trackers.
19
+
20
+ ## Workflow
21
+ 1. Parse namespaced source: `jira:KEY`, `github:ID`, or `gitlab:ID`.
22
+ 2. Read the item, current structure, and recent comments.
23
+ 3. Classify readiness gaps: missing outcome, acceptance criteria, ownership, dependencies, blockers, or decomposition.
24
+ 4. For every proposed child item, state intended owner, confidence, and fallback `unassigned`. Auto-assign only when the signal is strong from the tracker, comments, or existing team ownership patterns.
25
+ 5. Decide dependency handling explicitly for each child pair or blocker:
26
+ - no dependency
27
+ - description note only
28
+ - real tracker link
29
+ Prefer actual Jira `Blocks` links when child A must precede child B.
30
+ 6. Return one outcome:
31
+ - `ready-single`: one executable owner, no decomposition needed
32
+ - `ready-decomposed`: split is needed and children, links, and owners are clear
33
+ - `needs-decision`: missing product decision or not truly ready-decomposed because ownership/dependencies are still muddy
34
+ 7. Propose the smallest useful tracker edits and child items before mutating anything.
35
+ 8. Before any approved mutation, walk this checklist in order:
36
+ - preserve product-authored text
37
+ - append acceptance criteria instead of rewriting existing acceptance criteria
38
+ - create child?
39
+ - assign child?
40
+ - link child?
41
+ - then mutate parent text/fields
42
+ 9. On explicit approval, apply the approved tracker edits only.
43
+ 10. Re-run idempotently: reuse existing child items and any prior agent-owned refinement marker/comment.
44
+ 11. Recommend `/triage` only once the item is execution-ready.
45
+
46
+ ## Output contract
47
+ End with:
48
+ - **Outcome**: ready-single / ready-decomposed / needs-decision
49
+ - **Missing information**
50
+ - **Proposed tracker edits**
51
+ - **Proposed child work**
52
+ - **Proposed ownership**
53
+ - **Proposed dependency links**
54
+ - **Next step**: approve edits / answer question / `/triage`
@@ -0,0 +1,42 @@
1
+ """Emit bounded structural Graphify evidence from a disposable archive."""
2
+ import json
3
+ import sys
4
+ from contextlib import redirect_stdout
5
+ from pathlib import Path
6
+
7
+ EDGE_LIMIT = 100
8
+
9
+
10
+ def normalize_edge(edge):
11
+ return {
12
+ "confidence": edge.get("confidence", "EXTRACTED"),
13
+ "source": edge.get("source", edge.get("from", "")),
14
+ "target": edge.get("target", edge.get("to", "")),
15
+ "type": edge.get("type", edge.get("relation", "")),
16
+ }
17
+
18
+
19
+ def render_evidence(graph, limit=EDGE_LIMIT):
20
+ edges = graph.get("edges", [])[:limit]
21
+ return {
22
+ "edges": [normalize_edge(edge) for edge in edges],
23
+ "truncated": len(graph.get("edges", [])) > len(edges),
24
+ }
25
+
26
+
27
+ def main(root_arg):
28
+ root = Path(root_arg)
29
+ out = root / "graphify-out"
30
+ from graphify.extract import collect_files, extract
31
+
32
+ with redirect_stdout(sys.stderr):
33
+ graph = extract(collect_files(root), cache_root=out, root=root, parallel=False)
34
+ print(json.dumps(render_evidence(graph)))
35
+
36
+
37
+ if __name__ == "__main__":
38
+ try:
39
+ main(sys.argv[1])
40
+ except Exception as error:
41
+ print(f"Graphify extraction failed: {error}", file=sys.stderr)
42
+ raise
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ # Optional structural Graphify pass over committed HEAD only.
3
+ set -u
4
+
5
+ repo_root=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "WARN: Graphify skipped: not a git repository." >&2; exit 0; }
6
+ python=${GRAPHIFY_PYTHON:-"$HOME/.graphify/venv/bin/python"}
7
+ if [[ ! -x "$python" ]]; then
8
+ python="$repo_root/.graphify/venv/bin/python"
9
+ fi
10
+ script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
11
+ temp=$(mktemp -d "${TMPDIR:-/tmp}/norpiv-graphify.XXXXXX") || { echo "WARN: Graphify skipped: cannot create temporary directory." >&2; exit 0; }
12
+ trap 'rm -rf "$temp"' EXIT
13
+
14
+ if [[ ! -x "$python" ]]; then
15
+ echo "WARN: Graphify skipped: machine-wide or repo-local Python is unavailable." >&2
16
+ exit 0
17
+ fi
18
+ if ! git -C "$repo_root" archive HEAD | tar -x -C "$temp"; then
19
+ echo "WARN: Graphify skipped: cannot archive HEAD." >&2
20
+ exit 0
21
+ fi
22
+ if ! "$python" "$script_dir/graphify-grill.py" "$temp"; then
23
+ echo "WARN: Graphify failed; continue with normal source reading." >&2
24
+ fi
@@ -0,0 +1,21 @@
1
+ import importlib.util
2
+ from pathlib import Path
3
+
4
+ module_path = Path(__file__).with_name("graphify-grill.py")
5
+ spec = importlib.util.spec_from_file_location("graphify_grill", module_path)
6
+ module = importlib.util.module_from_spec(spec)
7
+ spec.loader.exec_module(module)
8
+
9
+ edge = module.normalize_edge({"from": "a", "to": "b", "relation": "calls"})
10
+ assert edge == {
11
+ "confidence": "EXTRACTED",
12
+ "source": "a",
13
+ "target": "b",
14
+ "type": "calls",
15
+ }
16
+
17
+ rendered = module.render_evidence({"edges": [{"source": str(i)} for i in range(105)]}, limit=100)
18
+ assert len(rendered["edges"]) == 100
19
+ assert rendered["truncated"] is True
20
+
21
+ print("graphify-grill.py self-check passed")
@@ -120,21 +120,12 @@ PY
120
120
 
121
121
  write_active_pointer() {
122
122
  mkdir -p ".workflow"
123
- python3 - ".workflow/active_task.json" ".workflow/active.json" "$TASK_FOLDER" "$SOURCE" "$ID" "$TASK_DIR" "$WORK_MD" "$BRANCH_NAME" "$ISO_NOW" <<'PY'
123
+ python3 - ".workflow/active.json" "$TASK_FOLDER" "$SOURCE" "$ID" "$TASK_DIR" "$WORK_MD" "$BRANCH_NAME" "$ISO_NOW" <<'PY'
124
124
  import json
125
125
  import sys
126
126
  from pathlib import Path
127
127
 
128
- active_task_path, active_workflow_path, active_task, source, raw_id, task_path, state_file, branch, now = sys.argv[1:]
129
- legacy_data = {
130
- "active_task": active_task,
131
- "source": source,
132
- "id": raw_id,
133
- "sourceId": raw_id,
134
- "taskPath": task_path,
135
- "path": task_path,
136
- "branch": branch,
137
- }
128
+ active_workflow_path, active_task, source, raw_id, task_path, state_file, branch, now = sys.argv[1:]
138
129
  workflow_data = {
139
130
  "workflow": "rpiv",
140
131
  "id": active_task,
@@ -147,9 +138,7 @@ workflow_data = {
147
138
  "branch": branch,
148
139
  "startedAt": now,
149
140
  "updatedAt": now,
150
- "compatPointer": ".workflow/active_task.json",
151
141
  }
152
- Path(active_task_path).write_text(json.dumps(legacy_data, indent=2) + "\n")
153
142
  Path(active_workflow_path).write_text(json.dumps(workflow_data, indent=2) + "\n")
154
143
  PY
155
144
  }
@@ -202,24 +191,43 @@ PY
202
191
  }
203
192
 
204
193
  update_work_meta() {
205
- local status phase
194
+ local status
206
195
  status=$(json_get "$METADATA_JSON" status)
207
- phase=$(json_get "$METADATA_JSON" phase)
208
196
  status=${status:-active}
209
- phase=${phase:-triaged}
210
197
 
211
- python3 - "$WORK_MD" "$BRANCH_NAME" "$status" "$phase" "$SOURCE" "$ID" <<'PY'
198
+ python3 - "$WORK_MD" "$BRANCH_NAME" "$status" "$SOURCE" "$ID" <<'PY'
212
199
  import re
213
200
  import sys
214
201
  from pathlib import Path
215
202
 
216
203
  path = Path(sys.argv[1])
217
- branch, status, phase, source, raw_id = sys.argv[2:]
204
+ branch, status, source, raw_id = sys.argv[2:]
218
205
  text = path.read_text() if path.exists() else ""
219
206
  lines = text.splitlines()
220
207
  header_re = re.compile(r"^(## )?\[[A-Z0-9_-]+\]\s*$")
221
208
  meta_re = re.compile(r"^(## )?\[META\]\s*$")
222
209
 
210
+
211
+ def section_body(section):
212
+ header = re.compile(rf"^(## )?\[{re.escape(section)}\]\s*$")
213
+ start = next((i for i, line in enumerate(lines) if header.match(line)), None)
214
+ if start is None:
215
+ return ""
216
+ end = len(lines)
217
+ for i in range(start + 1, len(lines)):
218
+ if header_re.match(lines[i]):
219
+ end = i
220
+ break
221
+ return "\n".join(lines[start + 1:end]).strip()
222
+
223
+
224
+ def meaningful(section):
225
+ body = section_body(section)
226
+ return any(line.strip() and line.strip() not in {"-", "- [ ]"} for line in body.splitlines())
227
+
228
+
229
+ phase = "planned" if meaningful("PLAN") else "grilled" if meaningful("GRILL") else "framed" if meaningful("BRIEF") else "triaged"
230
+
223
231
  new_block = [
224
232
  "## [META]",
225
233
  f"- Branch: `{branch}`",
@@ -247,6 +255,100 @@ path.write_text("\n".join(updated).rstrip() + "\n")
247
255
  PY
248
256
  }
249
257
 
258
+ write_work_snapshot() {
259
+ python3 - "$WORK_MD" "$METADATA_JSON" "$SOURCE" "$ID" <<'PY'
260
+ import json
261
+ import re
262
+ import sys
263
+ from pathlib import Path
264
+
265
+ work_md, metadata_json, source, raw_id = sys.argv[1:]
266
+
267
+ try:
268
+ metadata = json.loads(Path(metadata_json).read_text())
269
+ if not isinstance(metadata, dict):
270
+ metadata = {}
271
+ except Exception:
272
+ metadata = {}
273
+
274
+
275
+ def pick(*values, fallback=""):
276
+ for value in values:
277
+ if isinstance(value, str) and value.strip():
278
+ return value.strip()
279
+ return fallback
280
+
281
+
282
+ def squash(value):
283
+ if isinstance(value, dict):
284
+ value = json.dumps(value, ensure_ascii=False)
285
+ if not isinstance(value, str):
286
+ return ""
287
+ text = re.sub(r"```.*?```", " ", value, flags=re.S)
288
+ text = re.sub(r"^(## )?\[[A-Z0-9_-]+\]\s*$", " ", text, flags=re.M)
289
+ text = re.sub(r"\s+", " ", text).strip()
290
+ return text[:280].rstrip() + ("..." if len(text) > 280 else "")
291
+
292
+
293
+ fields = metadata.get("fields") if isinstance(metadata.get("fields"), dict) else {}
294
+ title = {
295
+ "github": pick(metadata.get("title"), fallback=f"GitHub #{raw_id}"),
296
+ "gitlab": pick(metadata.get("title"), fallback=f"GitLab #{raw_id}"),
297
+ "jira": pick(fields.get("summary"), metadata.get("key"), fallback=f"Jira {raw_id}"),
298
+ }.get(source, pick(metadata.get("title"), fallback=f"Local Task {raw_id}"))
299
+ url = {
300
+ "github": pick(metadata.get("url"), fallback=f"https://github.com/issues/{raw_id}"),
301
+ "gitlab": pick(metadata.get("web_url"), metadata.get("url")),
302
+ "jira": pick(metadata.get("self")),
303
+ }.get(source, "")
304
+ summary = {
305
+ "github": squash(metadata.get("body")),
306
+ "gitlab": squash(metadata.get("description")),
307
+ "jira": squash(fields.get("description")),
308
+ }.get(source, squash(metadata.get("body"))) or title
309
+ tracker_updated = {
310
+ "github": pick(metadata.get("updatedAt"), metadata.get("createdAt")),
311
+ "gitlab": pick(metadata.get("updated_at"), metadata.get("created_at")),
312
+ "jira": pick(fields.get("updated"), metadata.get("updated")),
313
+ }.get(source, "")
314
+
315
+ lines = [
316
+ f"# WORK: {title}",
317
+ "",
318
+ "## [INTAKE]",
319
+ "### Outcome",
320
+ summary,
321
+ "",
322
+ "### Acceptance Criteria",
323
+ "- [ ] Confirm concrete acceptance criteria from tracker or refine them during `/frame`.",
324
+ "",
325
+ "### Scope / Non-goals",
326
+ "- Keep local execution state concise; do not copy raw tracker or CLI rendering into `WORK.md`.",
327
+ "",
328
+ "### Dependencies / Blockers",
329
+ "- None noted from intake snapshot.",
330
+ "",
331
+ "### Tracker Context",
332
+ f"- Task: `{source}:{raw_id}`",
333
+ f"- URL: {url}" if url else "- URL: None noted",
334
+ f"- Tracker updated: `{tracker_updated}`" if tracker_updated else "- Tracker updated: None noted",
335
+ "",
336
+ "## [BRIEF]",
337
+ "- ",
338
+ "",
339
+ "## [GRILL]",
340
+ "- ",
341
+ "",
342
+ "## [PLAN]",
343
+ "- [ ] ",
344
+ "",
345
+ "## [LOG]",
346
+ ]
347
+
348
+ Path(work_md).write_text("\n".join(lines).rstrip() + "\n")
349
+ PY
350
+ }
351
+
250
352
  backfill_metadata() {
251
353
  json_upsert "$METADATA_JSON" \
252
354
  "id=$ID" \
@@ -254,21 +356,18 @@ backfill_metadata() {
254
356
  "branch=$BRANCH_NAME" \
255
357
  "taskFolder=$TASK_FOLDER" \
256
358
  "?status=active" \
257
- "?phase=triaged" \
258
359
  "createdAt=$ISO_NOW" \
259
360
  "updatedAt=$ISO_NOW"
260
361
  }
261
362
 
262
363
  set_metadata_status_phase() {
263
364
  local status="$1"
264
- local phase="$2"
265
365
  json_upsert "$METADATA_JSON" \
266
366
  "id=$ID" \
267
367
  "source=$SOURCE" \
268
368
  "branch=$BRANCH_NAME" \
269
369
  "taskFolder=$TASK_FOLDER" \
270
370
  "status=$status" \
271
- "phase=$phase" \
272
371
  "createdAt=$ISO_NOW" \
273
372
  "updatedAt=$ISO_NOW"
274
373
  }
@@ -281,24 +380,25 @@ create_task() {
281
380
  github)
282
381
  command -v gh >/dev/null 2>&1 || { echo "ERROR: gh CLI is required for github tasks."; exit 1; }
283
382
  echo "Fetching GitHub Issue #$ID..."
284
- gh issue view "$ID" --json title,body,author,labels,comments > "$METADATA_JSON"
285
- echo "# WORK: GitHub #$ID" > "$WORK_MD"
286
- gh issue view "$ID" >> "$WORK_MD"
383
+ gh issue view "$ID" --json title,body,author,labels,url,number,createdAt,updatedAt > "$METADATA_JSON"
287
384
  ;;
288
385
  gitlab)
289
386
  command -v glab >/dev/null 2>&1 || { echo "ERROR: glab CLI is required for gitlab tasks."; exit 1; }
290
387
  echo "Fetching GitLab Issue #$ID..."
291
- glab issue view "$ID" > "$WORK_MD"
292
- echo '{}' > "$METADATA_JSON"
388
+ if ! glab issue view "$ID" --output json > "$METADATA_JSON" 2>/dev/null; then
389
+ echo '{}' > "$METADATA_JSON"
390
+ json_upsert "$METADATA_JSON" "title=GitLab Issue $ID"
391
+ fi
293
392
  ;;
294
393
  jira)
295
394
  echo "Fetching Jira Ticket $ID..."
296
395
  if command -v jira >/dev/null 2>&1; then
297
- jira issue view "$ID" > "$WORK_MD"
298
396
  jira issue view "$ID" --raw > "$METADATA_JSON"
299
397
  elif command -v acli >/dev/null 2>&1; then
300
- acli jira workitem view "$ID" > "$WORK_MD"
301
- echo '{}' > "$METADATA_JSON"
398
+ if ! acli jira workitem view "$ID" --json > "$METADATA_JSON" 2>/dev/null; then
399
+ echo '{}' > "$METADATA_JSON"
400
+ json_upsert "$METADATA_JSON" "title=Jira $ID"
401
+ fi
302
402
  else
303
403
  echo "ERROR: jira or acli CLI is required for jira tasks."
304
404
  exit 1
@@ -306,8 +406,8 @@ create_task() {
306
406
  ;;
307
407
  local)
308
408
  echo "Initializing local task workspace: $ID..."
309
- echo "# WORK: Local Task $ID" > "$WORK_MD"
310
409
  echo '{}' > "$METADATA_JSON"
410
+ json_upsert "$METADATA_JSON" "title=Local Task $ID"
311
411
  ;;
312
412
  *)
313
413
  echo "Unknown source: $SOURCE"
@@ -315,11 +415,8 @@ create_task() {
315
415
  ;;
316
416
  esac
317
417
 
318
- set_metadata_status_phase "active" "triaged"
319
- ensure_section "BRIEF" "- "
320
- ensure_section "GRILL" "- "
321
- ensure_section "PLAN" "- [ ] "
322
- ensure_section "LOG" ""
418
+ set_metadata_status_phase "active"
419
+ write_work_snapshot
323
420
  update_work_meta
324
421
  append_log "Task initialized via /triage"
325
422
  write_active_pointer
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
3
  # validate_active_task.sh
4
- # Lightweight, print-only validation of .workflow/active_task.json and related files.
4
+ # Lightweight, print-only validation of .workflow/active.json and compatibility .workflow/active_task.json.
5
5
  # This script never mutates task state; it only reports inconsistencies and suggested fixes.
6
6
 
7
7
  set -euo pipefail
@@ -9,6 +9,7 @@ set -euo pipefail
9
9
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
10
  TRIAGE_HELPER="$SCRIPT_DIR/triage_helper.sh"
11
11
  REPO_ROOT=$(git rev-parse --show-toplevel)
12
+ ACTIVE_JSON="$REPO_ROOT/.workflow/active.json"
12
13
  ATF="$REPO_ROOT/.workflow/active_task.json"
13
14
  CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || true)
14
15
  WORK_MD=""
@@ -152,29 +153,52 @@ print_json_field_report() {
152
153
  done
153
154
  }
154
155
 
155
- if [[ ! -f "$ATF" ]]; then
156
- error "No active_task.json found at $ATF"
156
+ POINTER_LABEL=""
157
+ TASK_PATH=""
158
+ TASK_SOURCE=""
159
+ TASK_ID=""
160
+ ACTIVE_TASK=""
161
+ POINTER_BRANCH=""
162
+
163
+ if [[ -f "$ACTIVE_JSON" ]]; then
164
+ if ! python3 -m json.tool "$ACTIVE_JSON" >/dev/null 2>&1; then
165
+ error "active.json is not valid JSON: $ACTIVE_JSON"
166
+ exit 1
167
+ fi
168
+ POINTER_LABEL="active.json"
169
+ echo "Active workflow file: $ACTIVE_JSON"
170
+ json_pretty "$ACTIVE_JSON" || true
171
+
172
+ TASK_PATH=$(json_read "$ACTIVE_JSON" '.taskPath // .path')
173
+ TASK_SOURCE=$(json_read "$ACTIVE_JSON" '.source')
174
+ TASK_ID=$(json_read "$ACTIVE_JSON" '.sourceId // .id')
175
+ ACTIVE_TASK=$(json_read "$ACTIVE_JSON" '.taskId // .id')
176
+ POINTER_BRANCH=$(json_read "$ACTIVE_JSON" '.branch')
177
+
178
+ print_json_field_report "active.json" "$ACTIVE_JSON" workflow id taskId source sourceId taskPath path branch stateFile
179
+ elif [[ -f "$ATF" ]]; then
180
+ if ! python3 -m json.tool "$ATF" >/dev/null 2>&1; then
181
+ error "active_task.json is not valid JSON: $ATF"
182
+ exit 1
183
+ fi
184
+ POINTER_LABEL="active_task.json"
185
+ echo "Legacy active task file: $ATF"
186
+ json_pretty "$ATF" || true
187
+
188
+ TASK_PATH=$(json_read "$ATF" '.taskPath // .path')
189
+ TASK_SOURCE=$(json_read "$ATF" '.source')
190
+ TASK_ID=$(json_read "$ATF" '.sourceId // .id')
191
+ ACTIVE_TASK=$(json_read "$ATF" '.active_task')
192
+ POINTER_BRANCH=$(json_read "$ATF" '.branch')
193
+
194
+ print_json_field_report "active_task.json" "$ATF" active_task source id sourceId taskPath path branch
195
+ else
196
+ error "No active workflow pointer found at $ACTIVE_JSON or $ATF"
157
197
  echo "Suggested fixes (dry):"
158
198
  echo " - Run: $TRIAGE_HELPER <source> <id>"
159
199
  exit 1
160
200
  fi
161
201
 
162
- if ! python3 -m json.tool "$ATF" >/dev/null 2>&1; then
163
- error "active_task.json is not valid JSON: $ATF"
164
- exit 1
165
- fi
166
-
167
- echo "Active task file: $ATF"
168
- json_pretty "$ATF" || true
169
-
170
- TASK_PATH=$(json_read "$ATF" '.taskPath // .path')
171
- TASK_SOURCE=$(json_read "$ATF" '.source')
172
- TASK_ID=$(json_read "$ATF" '.sourceId // .id')
173
- ACTIVE_TASK=$(json_read "$ATF" '.active_task')
174
- POINTER_BRANCH=$(json_read "$ATF" '.branch')
175
-
176
- print_json_field_report "active_task.json" "$ATF" active_task source id sourceId taskPath path branch
177
-
178
202
  echo
179
203
  if [[ -n "$TASK_PATH" ]]; then
180
204
  if [[ "$TASK_PATH" = /* ]]; then
@@ -184,7 +208,7 @@ if [[ -n "$TASK_PATH" ]]; then
184
208
  fi
185
209
  echo "Using taskPath -> checking: $WORK_MD"
186
210
  elif [[ -n "$TASK_ID" ]]; then
187
- echo "No taskPath in active_task.json; attempting to derive from source+id"
211
+ echo "No taskPath in $POINTER_LABEL; attempting to derive from source+id"
188
212
  if [[ -n "$TASK_SOURCE" ]]; then
189
213
  TASK_FOLDER="$TASK_SOURCE-$TASK_ID"
190
214
  else
@@ -193,7 +217,7 @@ elif [[ -n "$TASK_ID" ]]; then
193
217
  WORK_MD="$REPO_ROOT/.workflow/tasks/$TASK_FOLDER/WORK.md"
194
218
  echo "Derived path: $WORK_MD"
195
219
  else
196
- error "Neither taskPath/path nor sourceId/id present in active_task.json"
220
+ error "Neither taskPath/path nor sourceId/id present in $POINTER_LABEL"
197
221
  fi
198
222
 
199
223
  if [[ -n "$WORK_MD" && -f "$WORK_MD" ]]; then
@@ -209,7 +233,7 @@ if [[ -n "$ACTIVE_TASK" && -n "$TASK_DIR" ]]; then
209
233
  if [[ "$ACTIVE_TASK" == "$ACTUAL_TASK_FOLDER" ]]; then
210
234
  ok "active_task matches task folder ($ACTIVE_TASK)"
211
235
  else
212
- warn "active_task ($ACTIVE_TASK) differs from task folder ($ACTUAL_TASK_FOLDER)"
236
+ warn "pointer task id ($ACTIVE_TASK) differs from task folder ($ACTUAL_TASK_FOLDER)"
213
237
  fi
214
238
  fi
215
239
 
@@ -225,8 +249,8 @@ META_ID=""
225
249
  META_SOURCE=""
226
250
  META_TASK_FOLDER=""
227
251
  META_STATUS=""
228
- META_PHASE=""
229
252
  META_BRANCH=""
253
+ DERIVED_PHASE=""
230
254
 
231
255
  if [[ -n "$META" ]]; then
232
256
  if [[ -f "$META" ]]; then
@@ -234,20 +258,19 @@ if [[ -n "$META" ]]; then
234
258
  echo
235
259
  echo "Metadata file: $META"
236
260
  json_pretty "$META" || true
237
- print_json_field_report "metadata.json" "$META" id source taskFolder status phase branch createdAt updatedAt
261
+ print_json_field_report "metadata.json" "$META" id source taskFolder status branch createdAt updatedAt
238
262
 
239
263
  META_ID=$(json_read "$META" '.id')
240
264
  META_SOURCE=$(json_read "$META" '.source')
241
265
  META_TASK_FOLDER=$(json_read "$META" '.taskFolder')
242
266
  META_STATUS=$(json_read "$META" '.status')
243
- META_PHASE=$(json_read "$META" '.phase')
244
267
  META_BRANCH=$(json_read "$META" '.branch')
245
268
 
246
269
  if [[ -n "$META_ID" && -n "$TASK_ID" && "$META_ID" != "$TASK_ID" ]]; then
247
- warn "metadata.id ($META_ID) differs from active_task id/sourceId ($TASK_ID)"
270
+ warn "metadata.id ($META_ID) differs from pointer id/sourceId ($TASK_ID)"
248
271
  fi
249
272
  if [[ -n "$META_SOURCE" && -n "$TASK_SOURCE" && "$META_SOURCE" != "$TASK_SOURCE" ]]; then
250
- warn "metadata.source ($META_SOURCE) differs from active_task.source ($TASK_SOURCE)"
273
+ warn "metadata.source ($META_SOURCE) differs from pointer source ($TASK_SOURCE)"
251
274
  fi
252
275
  if [[ -n "$META_TASK_FOLDER" && -n "$TASK_DIR" && "$META_TASK_FOLDER" != "$(basename "$TASK_DIR")" ]]; then
253
276
  warn "metadata.taskFolder ($META_TASK_FOLDER) differs from task folder ($(basename "$TASK_DIR"))"
@@ -275,16 +298,19 @@ if [[ -n "$WORK_MD" && -f "$WORK_MD" ]]; then
275
298
  esac
276
299
  done
277
300
 
278
- if [[ "$META_PHASE" =~ ^(planned|implementing|verifying|synced|closed)$ ]]; then
279
- if [[ "$(has_plan_checkbox "$WORK_MD")" == "yes" ]]; then
280
- ok "[PLAN] contains checkboxes for phase=$META_PHASE"
281
- else
282
- warn "phase=$META_PHASE but [PLAN] has no checkboxes"
283
- fi
301
+ if [[ "$(section_body_nonempty "PLAN" "$WORK_MD")" == "yes" ]]; then
302
+ DERIVED_PHASE="planned"
303
+ elif [[ "$(section_body_nonempty "GRILL" "$WORK_MD")" == "yes" ]]; then
304
+ DERIVED_PHASE="grilled"
305
+ elif [[ "$(section_body_nonempty "BRIEF" "$WORK_MD")" == "yes" ]]; then
306
+ DERIVED_PHASE="framed"
307
+ else
308
+ DERIVED_PHASE="triaged"
284
309
  fi
310
+ ok "derived phase from WORK.md: $DERIVED_PHASE"
285
311
 
286
- if [[ "$META_PHASE" == "triaged" && "$(section_body_nonempty "BRIEF" "$WORK_MD")" == "yes" ]]; then
287
- warn "phase=triaged but [BRIEF] appears populated; consider updating phase metadata"
312
+ if [[ "$DERIVED_PHASE" == "planned" && "$(has_plan_checkbox "$WORK_MD")" != "yes" ]]; then
313
+ warn "derived phase=planned but [PLAN] has no checkboxes"
288
314
  fi
289
315
  fi
290
316
 
@@ -296,12 +322,12 @@ echo
296
322
  echo "Suggested fixes (dry):"
297
323
  if [[ -n "$WORK_MD" && ! -f "$WORK_MD" ]]; then
298
324
  echo " - Re-run triage: $TRIAGE_HELPER <source> <id>"
299
- echo " - Or update .workflow/active_task.json to include a correct taskPath or source+id"
325
+ echo " - Or update the active workflow pointer to include a correct taskPath or source+id"
300
326
  fi
301
327
  if [[ -n "$META" && ! -f "$META" ]]; then
302
328
  echo " - Resume/backfill metadata: $TRIAGE_HELPER ${TASK_SOURCE:-local} ${TASK_ID:-task} resume"
303
329
  elif [[ -n "$META" && -f "$META" ]]; then
304
- if [[ -z "$META_STATUS" || -z "$META_PHASE" || -z "$(json_read "$META" '.createdAt')" || -z "$(json_read "$META" '.updatedAt')" ]]; then
330
+ if [[ -z "$META_STATUS" || -z "$(json_read "$META" '.createdAt')" || -z "$(json_read "$META" '.updatedAt')" ]]; then
305
331
  echo " - Backfill missing metadata fields: $TRIAGE_HELPER ${TASK_SOURCE:-local} ${TASK_ID:-task} resume"
306
332
  fi
307
333
  fi
package/sync/SKILL.md CHANGED
@@ -10,11 +10,13 @@ description: Synchronizes local RPIV task state (WORK.md) to external trackers (
10
10
  Maintains consistency between local `.workflow` state and the remote source of truth using a single Pi-owned living status comment per task.
11
11
 
12
12
  ## Guardrails
13
- - **Pre-flight**: Always read `.workflow/active.json` / `.workflow/active_task.json` and the active `WORK.md` before executing.
13
+ - **Pre-flight**: Always read `.workflow/active.json` first, then compatibility `.workflow/active_task.json` only if needed, and the active `WORK.md` before executing.
14
14
  - **Privacy**: NEVER sync secrets, environment variables, or private notes not intended for stakeholders.
15
15
  - **Integrity**: Do not modify `[BRIEF]` or `[GRILL]` sections.
16
16
  - **Idempotency**: If the remote Pi status already reflects the current local state, do not post or update.
17
17
  - **Human safety**: NEVER edit human-authored comments. Only update comments/notes containing the Pi sync marker.
18
+ - **Target ownership**: Sync the executable child issue/MR/PR that the work completed, not the umbrella parent, unless the user explicitly asks for parent status. If the active GitHub issue has sub-issues, verify the PR/body/current request points to the right child before posting.
19
+ - **Shell safety**: Never pass markdown bodies inline through shell strings. Write bodies to files and use `--body-file` or JSON `--input` API calls so backticks and `$()` cannot execute.
18
20
 
19
21
  ## Living status marker
20
22
  Every sync body MUST include this marker at the end:
@@ -46,7 +48,12 @@ Do **not** use latest-comment ownership as the primary decision. Latest-comment-
46
48
  ## Workflow
47
49
 
48
50
  ### 1. Discovery & State Loading
49
- - Identify the platform and ID from `.workflow/active.json` or compatibility `.workflow/active_task.json`.
51
+ - Identify the platform and ID from `.workflow/active.json`, falling back to compatibility `.workflow/active_task.json` only when required.
52
+ - For GitHub, check hierarchy before syncing umbrella issues:
53
+ ```bash
54
+ gh issue view <id> --json parent,subIssues --jq '{parent:.parent, subIssues:.subIssues}'
55
+ ```
56
+ If the issue has sub-issues and the work maps to one child, switch the sync target to that child or ask once. Do not sync a child deliverable to the parent just because the active task points at the parent.
50
57
  - Extract **Slices** from `[PLAN]`, **Status** from `[LOG]`, and **Artifacts** such as PR/MR links, commit hashes, and verification output.
51
58
 
52
59
  ### 2. Payload Preparation
@@ -81,8 +88,9 @@ gh api repos/:owner/:repo/issues/<id>/comments --paginate \
81
88
 
82
89
  Update:
83
90
  ```bash
91
+ jq -n --rawfile body body.md '{body: $body}' > body.json
84
92
  gh api -X PATCH repos/:owner/:repo/issues/comments/<comment_id> \
85
- -f body=@body.md
93
+ --input body.json
86
94
  ```
87
95
 
88
96
  Create:
@@ -94,6 +102,7 @@ Rules:
94
102
  - update only a comment containing the marker
95
103
  - no-op when normalized body is already current
96
104
  - create only when no marker comment exists
105
+ - keep markdown in files; do not use inline `-f body="$(cat body.md)"` or shell-expanded PR/comment bodies
97
106
 
98
107
  #### GitLab Issues / MRs
99
108
  Use notes API for issues or merge requests.
package/triage/SKILL.md CHANGED
@@ -10,10 +10,10 @@ description: "Ingest or resume a tracked/local task in the RPIV workspace. Use w
10
10
  Start RPIV by creating, resuming, or explicitly reopening a task workspace.
11
11
 
12
12
  ## Guardrails
13
- - READ: user argument, `.workflow/active.json` / `.workflow/active_task.json` if present, target `metadata.json`, and target `WORK.md` if resuming.
14
- - WRITE: `.workflow/tasks/[source-id]/WORK.md`, `.workflow/tasks/[source-id]/metadata.json`, `.workflow/active.json`, `.workflow/active_task.json`; optional `.reposcry/` cache files only if RepoScry is installed.
13
+ - READ: user argument, `.workflow/active.json` first and compatibility `.workflow/active_task.json` only if present/needed, target `metadata.json`, and target `WORK.md` if resuming.
14
+ - WRITE: `.workflow/tasks/[source-id]/WORK.md`, `.workflow/tasks/[source-id]/metadata.json`, `.workflow/active.json`.
15
15
  - On **create**, initialize required guarded sections only if absent: `[BRIEF]`, `[GRILL]`, `[PLAN]`, `[LOG]`, `[META]`.
16
- - On **resume**, only update `.workflow/active.json`, `.workflow/active_task.json`, metadata timestamps/state as needed, and `[META]`, then append one concise `[LOG]` entry.
16
+ - On **resume**, only update `.workflow/active.json`, metadata timestamps/state as needed, and `[META]`, then append one concise `[LOG]` entry.
17
17
  - NEVER: duplicate guarded sections.
18
18
  - NEVER: overwrite existing `[BRIEF]`, `[GRILL]`, or `[PLAN]` during triage.
19
19
  - NEVER: create `PROBLEM.md`, `PRD.md`, `PLAN.md`, or `EVIDENCE.md`.
@@ -60,17 +60,14 @@ Start RPIV by creating, resuming, or explicitly reopening a task workspace.
60
60
  - Preferred absolute path: `<skill_dir>/../scripts/triage_helper.sh`.
61
61
  3. Read the resulting active pointer, metadata, and `WORK.md`.
62
62
  4. Determine action based on helper output and task state: `created`, `resumed`, `reopened`, `refused`, `fresh/reset`.
63
- 5. Optional RepoScry bootstrap: if bundled `../scripts/reposcry-bootstrap.sh` available, run it to seed `.reposcry/` for later `/frame` and `/grill-with-docs`.
64
- - Run `../scripts/reposcry-bootstrap.sh --pulse` to determine "Repo Pulse" (Warm/Cold/Missing) for the output contract.
65
- - Run `../scripts/reposcry-bootstrap.sh` to ensure the local cache is initialized.
66
- - The helper must ensure `.reposcry/` is ignored, must stop if `.reposcry/` tracked/staged, and continue normally if RepoScry is unavailable.
67
- 6. Verify branch: ensure current branch matches metadata `branch` (or is `main`/`master` for planning).
68
- 7. If task was newly created, infer classification:
63
+ 6. New task creation should store structured tracker facts in `metadata.json` and write a concise normalized intake snapshot into `WORK.md`; never paste raw tracker CLI rendering.
64
+ 7. Verify branch: ensure current branch matches metadata `branch` (or is `main`/`master` for planning).
65
+ 8. If task was newly created, infer classification:
69
66
  - `github:`, `gitlab:`, `jira:` labels/type often indicate Problem (bug) or Proposal (feature).
70
67
  - Local tasks default to Proposal unless specified.
71
- 8. If resuming, check `[BRIEF]` and `[PLAN]` completion status.
72
- 9. **Final Guidance**: record current branch in `[META]`. For `jira:` tasks, also preserve the Jira key in `[META]` and mention `/implement` must use it in the commit subject. Planning on `main`/`master` allowed; implementation will use `/implement` branch enforcement.
73
- 10. End by recommending next valid command:
68
+ 9. If resuming, check `[BRIEF]` and `[PLAN]` completion status.
69
+ 10. **Final Guidance**: record current branch in `[META]`. For `jira:` tasks, preserve the Jira key in `[META]`. Planning on `main`/`master` allowed; implementation will use `/implement` branch enforcement.
70
+ 11. End by recommending next valid command:
74
71
  - newly created -> `/frame`
75
72
  - existing empty `[BRIEF]` -> `/frame`
76
73
  - framed but not grilled -> `/grill-with-docs`
@@ -85,6 +82,5 @@ End with:
85
82
  - **Status**: active / blocked / done / archived / unknown
86
83
  - **Phase**: triaged / framed / grilled / planned / implementing / verifying / synced / closed / unknown
87
84
  - **Branch**: current branch
88
- - **Repo Pulse**: Warm / Cold / Missing / Not applicable
89
85
  - **Classification**: Problem / Proposal
90
86
  - **Next step**: `/frame`, `/grill-with-docs`, `/plan`, `/implement`, or explicit reopen/fresh choice
package/verify/SKILL.md CHANGED
@@ -10,7 +10,7 @@ description: Verify the active slice or task against WORK.md, quality gates, and
10
10
  The final gate for a slice or task. Verify truth before reporting progress.
11
11
 
12
12
  ## Guardrails
13
- - READ: `.workflow/active.json` / `.workflow/active_task.json`, active `WORK.md` `[BRIEF]`, `[PLAN]`, and `[LOG]`.
13
+ - READ: `.workflow/active.json` first, then compatibility `.workflow/active_task.json` only if needed, plus active `WORK.md` `[BRIEF]`, `[PLAN]`, and `[LOG]`.
14
14
  - WRITE: `WORK.md` -> `[PLAN]` checkboxes and append to `[LOG]` only.
15
15
  - NEVER: add `Signed-off-by`; tell the human to sign if needed.
16
16
  - NEVER: transition tracker state if verification fails.
@@ -19,9 +19,8 @@ The final gate for a slice or task. Verify truth before reporting progress.
19
19
  ## Workflow
20
20
  1. Compare code changes against `[BRIEF]` and the current `[PLAN]` slice.
21
21
  2. Run stated verification commands and available quality gates.
22
- 3. If RepoScry is available, optionally add graph-aware evidence with commands such as `reposcry validate main HEAD` and `reposcry --repo . get_affected_flows main HEAD`. Treat RepoScry as supplemental evidence, not a hard requirement. Verify `.reposcry/` is not staged or tracked before reporting review readiness.
23
22
  4. Check for AI artifacts: placeholder comments, fake APIs, dead code, inconsistent naming.
24
- 5. Confirm commit messages include Conventional Commit format. When AI contributed, you MUST append an `Assisted-by` trailer. Do NOT guess or hallucinate the model name from your system prompt. You must run `bash ~/RQZ/personal/nothing/packages/norpiv/scripts/get-pi-model.sh` and use its exact output to construct the trailer: `Assisted-by: <EXACT_OUTPUT> [tools]`.
23
+ 5. Confirm commits and PR/MR metadata satisfy the repository's own hooks, CI, and release policies. Do not invent or enforce extra RPIV-specific formatting here.
25
24
  6. If passing, mark the slice checkbox complete in `[PLAN]` and append verification evidence to `[LOG]` (Format: `YYYY-MM-DD hh:mm AM/PM`).
26
25
  7. Recommend `/sync` for tracker update, or `/post-merge-prune` if the task is fully merged and user approves.
27
26
 
@@ -1,108 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- set -euo pipefail
4
-
5
- REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
6
- cd "$REPO_ROOT"
7
-
8
- ensure_reposcry_gitignore() {
9
- local gitignore=".gitignore"
10
- touch "$gitignore"
11
-
12
- if ! grep -qxF ".reposcry/" "$gitignore"; then
13
- {
14
- printf '\n# RepoScry local cache\n'
15
- printf '.reposcry/\n'
16
- } >> "$gitignore"
17
- echo "Added .reposcry/ to .gitignore"
18
- fi
19
- }
20
-
21
- refuse_tracked_reposcry_cache() {
22
- local tracked staged
23
- tracked=$(git ls-files .reposcry 2>/dev/null || true)
24
- staged=$(git diff --cached --name-only -- .reposcry 2>/dev/null || true)
25
-
26
- if [[ -n "$tracked" || -n "$staged" ]]; then
27
- echo "ERROR: .reposcry/ cache is tracked or staged. It is generated local state and must not be committed." >&2
28
- if [[ -n "$tracked" ]]; then
29
- echo "Tracked files:" >&2
30
- printf '%s\n' "$tracked" >&2
31
- fi
32
- if [[ -n "$staged" ]]; then
33
- echo "Staged files:" >&2
34
- printf '%s\n' "$staged" >&2
35
- fi
36
- echo "Fix: git rm --cached -r .reposcry && keep .reposcry/ in .gitignore" >&2
37
- exit 1
38
- fi
39
- }
40
-
41
- reposcry_indexed_files() {
42
- reposcry stats 2>/dev/null | awk '/Files indexed:/ {print $3; exit}'
43
- }
44
-
45
- # Quick health check for Pulse info
46
- check_pulse() {
47
- if ! command -v reposcry >/dev/null 2>&1; then
48
- echo "PULSE: Missing (Not installed)"
49
- return
50
- fi
51
- if [[ ! -d .reposcry ]]; then
52
- echo "PULSE: Cold (No cache)"
53
- return
54
- fi
55
- local files
56
- files=$(reposcry_indexed_files || echo "0")
57
- if [[ "$files" == "0" ]]; then
58
- echo "PULSE: Cold (Zero files)"
59
- else
60
- echo "PULSE: Warm ($files files)"
61
- fi
62
- }
63
-
64
- if [[ "${1:-}" == "--pulse" ]]; then
65
- check_pulse
66
- exit 0
67
- fi
68
-
69
- ensure_reposcry_gitignore
70
- refuse_tracked_reposcry_cache
71
-
72
- if ! command -v reposcry >/dev/null 2>&1; then
73
- echo "RepoScry not installed; skipping bootstrap."
74
- exit 0
75
- fi
76
-
77
- HAD_REPOSCRYIGNORE=0
78
- [[ -f .reposcryignore ]] && HAD_REPOSCRYIGNORE=1
79
-
80
- FORCE_REINDEX=0
81
- if [[ "${1:-}" == "--force" || "${1:-}" == "--reindex" ]]; then
82
- FORCE_REINDEX=1
83
- fi
84
-
85
- if [[ ! -d .reposcry ]]; then
86
- echo "Initializing RepoScry..."
87
- reposcry init >/dev/null 2>&1 || reposcry init || {
88
- echo "RepoScry init failed; continuing without RepoScry." >&2
89
- exit 0
90
- }
91
- fi
92
-
93
- INDEXED_FILES=$(reposcry_indexed_files || true)
94
- INDEXED_FILES=${INDEXED_FILES:-0}
95
-
96
- if [[ $FORCE_REINDEX -eq 1 || "$INDEXED_FILES" == "0" ]]; then
97
- echo "Indexing repository with RepoScry (--no-semantic)..."
98
- reposcry index --no-semantic || {
99
- echo "RepoScry index failed; continuing without RepoScry." >&2
100
- exit 0
101
- }
102
- else
103
- echo "RepoScry index already present ($INDEXED_FILES files indexed)."
104
- fi
105
-
106
- if [[ $HAD_REPOSCRYIGNORE -eq 0 && -f .reposcryignore ]]; then
107
- echo "RepoScry created .reposcryignore. Review and commit it if you want stable RepoScry indexing rules."
108
- fi
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- set -euo pipefail
4
-
5
- BASE_REF=${1:-${REPOSCRY_BASE_REF:-main}}
6
-
7
- if ! command -v reposcry-update >/dev/null 2>&1; then
8
- echo "reposcry-update not installed; skipping refresh."
9
- exit 0
10
- fi
11
-
12
- REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
13
- cd "$REPO_ROOT"
14
-
15
- echo "Refreshing RepoScry for changed files against base '$BASE_REF'..."
16
- if ! reposcry-update --changed --base "$BASE_REF"; then
17
- echo "RepoScry refresh failed; continuing without refreshed graph state." >&2
18
- exit 0
19
- fi
@@ -1,40 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- set -euo pipefail
4
-
5
- TASK_TEXT=${*:-}
6
- if [[ -z "$TASK_TEXT" ]]; then
7
- echo "Usage: $0 <task summary>"
8
- exit 1
9
- fi
10
-
11
- if ! command -v reposcry >/dev/null 2>&1; then
12
- echo "RepoScry not installed; skipping task context generation."
13
- exit 0
14
- fi
15
-
16
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
17
- REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
18
- cd "$REPO_ROOT"
19
-
20
- "$SCRIPT_DIR/reposcry-bootstrap.sh" || true
21
-
22
- mkdir -p .reposcry
23
-
24
- # Detect low-token modes (Caveman, etc.)
25
- DEFAULT_BUDGET=20000
26
- if [[ "${CAVEMAN_MODE:-off}" != "off" ]] || [[ "${LOW_TOKEN_MODE:-0}" == "1" ]]; then
27
- DEFAULT_BUDGET=5000
28
- echo "Low-token mode detected: reducing context budget to $DEFAULT_BUDGET"
29
- fi
30
-
31
- BUDGET=${REPOSCRY_CONTEXT_BUDGET:-$DEFAULT_BUDGET}
32
- OUTPUT_PATH=".reposcry/AI_CONTEXT.md"
33
-
34
- echo "Generating RepoScry task context at $OUTPUT_PATH..."
35
- if reposcry context "$TASK_TEXT" --strict --budget "$BUDGET" --format markdown > "$OUTPUT_PATH"; then
36
- echo "Wrote $OUTPUT_PATH"
37
- else
38
- echo "RepoScry context generation failed; leaving RPIV flow unchanged." >&2
39
- rm -f "$OUTPUT_PATH"
40
- fi