diff --git a/discovery-map/INDEX.md b/discovery-map/INDEX.md index f96432a5..42258449 100644 --- a/discovery-map/INDEX.md +++ b/discovery-map/INDEX.md @@ -18,7 +18,7 @@ Each phase is its own PR off the previous phase's branch (stacked PRs — see *B 6. **[Refinement Session](phase-06-refinement-session.md)** — re-entry path, map editing operations, safety-by-destructiveness. **Status:** Review (PR #271) 7. **[Self-Healing Re-point](phase-07-self-healing.md)** — research-analysis and gap-analysis re-point to map at continue-epic boot-up. **Status:** Review (PR #272) 8. **[Imports](phase-08-imports.md)** — `imports/` directory, manifest tracking, KB indexing, behaviour change for features. **Status:** Review (PR #273) -9. **[Topic Splitting and Elevation](phase-09-topic-splitting-elevation.md)** — write inception items alongside; name collision validation. **Status:** Not started +9. **[Topic Splitting and Elevation](phase-09-topic-splitting-elevation.md)** — write inception items alongside; name collision validation. **Status:** Review (PR #274) 10. **[Direct-Entry Auto-Add](phase-10-direct-entry-auto-add.md)** — `d`/`discuss` and `r`/`research` for unmapped topics auto-create map items. **Status:** Not started 11. **[Migration](phase-11-migration.md)** — seed inception items for existing in-progress epics. **Status:** Not started 12. **[Drop Explore Mode](phase-12-drop-explore-mode.md)** — remove research's `e`/`explore`; collapse start-epic's `route-first-phase`. **Status:** Not started @@ -93,7 +93,7 @@ The merge sequence at the end of the initiative is **strictly bottom-to-top of t | `idea/inception-pr-6-refinement` | Phase 5 branch | Phase 6 — Refinement Session | [#271](https://github.com/leeovery/agentic-workflows/pull/271) | Review | | `idea/inception-pr-7-self-healing` | Phase 6 branch | Phase 7 — Self-Healing Re-point | [#272](https://github.com/leeovery/agentic-workflows/pull/272) | Review | | `idea/inception-pr-8-imports` | Phase 7 branch | Phase 8 — Imports | [#273](https://github.com/leeovery/agentic-workflows/pull/273) | Review | -| `idea/inception-pr-9-split-elevation` | Phase 7 branch | Phase 9 — Topic Splitting and Elevation | — | Not started | +| `idea/inception-pr-9-split-elevation` | Phase 7 branch | Phase 9 — Topic Splitting and Elevation | [#274](https://github.com/leeovery/agentic-workflows/pull/274) | Review | | `idea/inception-pr-10-direct-entry` | Phase 7 branch | Phase 10 — Direct-Entry Auto-Add | — | Not started | | `idea/inception-pr-11-migration` | Phase 5 branch | Phase 11 — Migration | — | Not started | | `idea/inception-pr-12-drop-explore` | Phase 11 branch | Phase 12 — Drop Explore Mode | — | Not started | diff --git a/skills/workflow-discussion-process/SKILL.md b/skills/workflow-discussion-process/SKILL.md index 9ed26bc0..14924b09 100644 --- a/skills/workflow-discussion-process/SKILL.md +++ b/skills/workflow-discussion-process/SKILL.md @@ -1,7 +1,7 @@ --- name: workflow-discussion-process user-invocable: false -allowed-tools: Bash(node .claude/skills/workflow-manifest/scripts/manifest.cjs), Bash(node .claude/skills/workflow-knowledge/scripts/knowledge.cjs) +allowed-tools: Bash(node .claude/skills/workflow-manifest/scripts/manifest.cjs), Bash(node .claude/skills/workflow-knowledge/scripts/knowledge.cjs), Bash(node .claude/skills/workflow-inception-process/scripts/discovery.cjs) --- # Discussion Process diff --git a/skills/workflow-discussion-process/references/discussion-session.md b/skills/workflow-discussion-process/references/discussion-session.md index d1585bac..f2f9377e 100644 --- a/skills/workflow-discussion-process/references/discussion-session.md +++ b/skills/workflow-discussion-process/references/discussion-session.md @@ -125,16 +125,40 @@ During organic discussion, a subtopic may grow beyond the scope of the current t #### If `elevate` -1. Create a seed discussion file at `.workflows/{work_unit}/discussion/{new-topic}.md` with: +1. Pick a kebab-case name reflecting the elevated concern. Surface it to the user for confirmation. Then validate: + + → Load **[topic-name-validation.md](../../workflow-shared/references/topic-name-validation.md)** with work_unit = `{work_unit}`, proposed_name = `{new-topic}`. + + On `collision-active`, re-prompt for an alternative and re-validate — loop until `ok` or `matches-dismissed`, or the user cancels the elevation. On `matches-dismissed`, proceed (the dismissed entry is pulled in step 4). On `ok`, proceed. + +2. Generate a one-sentence summary of the elevated concern (drawn from the context that triggered elevation). This becomes the inception item's `summary` field. + +3. Create the seed discussion file at `.workflows/{work_unit}/discussion/{new-topic}.md` with: - Context section capturing what prompted the topic and any initial thinking from the current discussion - A Discussion Map with initial subtopics derived from what's been discussed so far - No decisions — those happen in the new discussion -2. Register in manifest: + +4. Write manifest items — discussion first, then inception. If validation returned `matches-dismissed`, pull from the dismissed list first: + + ```bash + node .claude/skills/workflow-manifest/scripts/manifest.cjs pull {work_unit}.inception dismissed "{new-topic}" + ``` + + Then: + ```bash node .claude/skills/workflow-manifest/scripts/manifest.cjs init-phase {work_unit}.discussion.{new-topic} + node .claude/skills/workflow-manifest/scripts/manifest.cjs init-phase {work_unit}.inception.{new-topic} + node .claude/skills/workflow-manifest/scripts/manifest.cjs set {work_unit}.inception.{new-topic} routing discussion + node .claude/skills/workflow-manifest/scripts/manifest.cjs set {work_unit}.inception.{new-topic} summary "{one-line summary}" + node .claude/skills/workflow-manifest/scripts/manifest.cjs set {work_unit}.inception.{new-topic} source "discussion-elevation:{topic}" ``` -3. Update the current Discussion Map: replace the subtopic with `→ Elevated: {new-topic}` -4. Commit: `discussion({work_unit}/{topic}): elevate {new-topic} to separate discussion` + + `routing: discussion` because elevation fires inside a discussion session. `source: discussion-elevation:{parent_topic}` is historical provenance; no cascade. + +5. Update the current Discussion Map — replace the subtopic with `→ Elevated: {new-topic}`. + +6. Commit: `discussion({work_unit}/{topic}): elevate {new-topic} to separate discussion` → Return to **B. Session Loop**. diff --git a/skills/workflow-inception-process/references/map-operations.md b/skills/workflow-inception-process/references/map-operations.md index 4b509940..a288274c 100644 --- a/skills/workflow-inception-process/references/map-operations.md +++ b/skills/workflow-inception-process/references/map-operations.md @@ -77,16 +77,15 @@ Render the rejection in a code block: - `decided` — `discussion has concluded` - `cancelled` — `it has phase work in cancelled state and stays on the map as historical record` -**Name collision gates** — for Add and Rename, check the new name against `discovery_map`'s topic names (case-sensitive). A match means an **active** map item already uses the name — reject: +**Name validation** — for each Add and Rename operation, validate the proposed name via the shared reference: -> *Output the next fenced block as a code block:* +→ Load **[topic-name-validation.md](../../workflow-shared/references/topic-name-validation.md)** with work_unit = `{work_unit}`, proposed_name = `{name}`. -``` -"{name}" is already on the map. Pick a different name or use -edit-summary / change-routing on the existing item. -``` +Branch on `result`: -For Add, a name appearing in `dismissed` is **allowed** — it counts as a re-add. The Add flow pulls the name from the dismissed list before creating the new item. +- `collision-active` — rejection already rendered by the reference. Remove the operation from its group. +- `matches-dismissed` — allowed. For Add, the **D. Add** flow pulls the name from `dismissed` before writing. For Rename, proceed without pulling (a Rename target that happens to match a dismissed name leaves the dismissed entry alone; the new active item simply exists alongside it as historical record). +- `ok` — proceed. → Proceed to **C. Apply**. diff --git a/skills/workflow-research-process/SKILL.md b/skills/workflow-research-process/SKILL.md index 8a9dfe1c..622d4ed9 100644 --- a/skills/workflow-research-process/SKILL.md +++ b/skills/workflow-research-process/SKILL.md @@ -1,7 +1,7 @@ --- name: workflow-research-process user-invocable: false -allowed-tools: Bash(node .claude/skills/workflow-manifest/scripts/manifest.cjs), Bash(node .claude/skills/workflow-knowledge/scripts/knowledge.cjs) +allowed-tools: Bash(node .claude/skills/workflow-manifest/scripts/manifest.cjs), Bash(node .claude/skills/workflow-knowledge/scripts/knowledge.cjs), Bash(node .claude/skills/workflow-inception-process/scripts/discovery.cjs) --- # Research Process diff --git a/skills/workflow-research-process/references/topic-splitting.md b/skills/workflow-research-process/references/topic-splitting.md index 84b83418..657f631a 100644 --- a/skills/workflow-research-process/references/topic-splitting.md +++ b/skills/workflow-research-process/references/topic-splitting.md @@ -35,15 +35,43 @@ Want to split these into separate research files? #### If yes For each split topic: -1. Create `.workflows/{work_unit}/research/{topic}.md` using **[template.md](template.md)** -2. Move content verbatim from the source file — reword only for flow and readability, no summarisation -3. Remove the extracted content from the source file -4. Init manifest item for the new topic: + +1. Pick a kebab-case name from the thread's content (e.g. `image-moderation`, `kitchen-utensils`). Surface it to the user for confirmation. Then validate: + + → Load **[topic-name-validation.md](../../workflow-shared/references/topic-name-validation.md)** with work_unit = `{work_unit}`, proposed_name = `{new_topic}`. + + On `collision-active`, re-prompt for an alternative and re-validate — loop until `ok` or `matches-dismissed`, or the user abandons this thread. On `matches-dismissed`, proceed (the dismissed entry is pulled in step 4). On `ok`, proceed. + +2. Create `.workflows/{work_unit}/research/{new_topic}.md` using **[template.md](template.md)**. Move content verbatim from the source file — reword only for flow and readability, no summarisation. Remove the extracted content from the source file. + +3. Generate a one-sentence summary of the extracted content (drawn from the thread itself). This becomes the inception item's `summary` field, used in map renders. + +4. Write manifest items — research first, then inception. If the validation returned `matches-dismissed`, pull from the dismissed list first: + ```bash - node .claude/skills/workflow-manifest/scripts/manifest.cjs init-phase {work_unit}.research.{topic} + node .claude/skills/workflow-manifest/scripts/manifest.cjs pull {work_unit}.inception dismissed "{new_topic}" ``` -Commit after splitting. + Then: + + ```bash + node .claude/skills/workflow-manifest/scripts/manifest.cjs init-phase {work_unit}.research.{new_topic} + node .claude/skills/workflow-manifest/scripts/manifest.cjs init-phase {work_unit}.inception.{new_topic} + node .claude/skills/workflow-manifest/scripts/manifest.cjs set {work_unit}.inception.{new_topic} routing research + node .claude/skills/workflow-manifest/scripts/manifest.cjs set {work_unit}.inception.{new_topic} summary "{one-line summary}" + node .claude/skills/workflow-manifest/scripts/manifest.cjs set {work_unit}.inception.{new_topic} source "research-split:{parent_topic}" + ``` + + `routing: research` because the split fires inside a research session — research is where the new topic enters the pipeline. `source: research-split:{parent_topic}` is historical provenance; the parent's later state changes don't cascade. + +Once all accepted threads have been processed, single commit covering the manifest writes and the new research files: + +```bash +git add -- .workflows/{work_unit}/manifest.json .workflows/{work_unit}/research/ +git commit -m "research({work_unit}/{parent_topic}): split into {N} topic(s)" +``` + +Then offer the user a choice of which topic to continue with: > *Output the next fenced block as markdown (not a code block):* diff --git a/skills/workflow-shared/references/topic-name-validation.md b/skills/workflow-shared/references/topic-name-validation.md new file mode 100644 index 00000000..9541177c --- /dev/null +++ b/skills/workflow-shared/references/topic-name-validation.md @@ -0,0 +1,96 @@ +# Topic Name Validation + +*Shared reference. Loaded by `workflow-inception-process`, `workflow-research-process`, `workflow-discussion-process`, and any flow that proposes a new topic name for the discovery map.* + +--- + +Validates a proposed topic name. First step normalises to kebab-case silently (callers always have Claude pick the name, so a slip should self-correct rather than escalate). Then checks against active discovery-map items (collision rejects) and the dismissed list (informational — caller pulls before writing). Returns a `result` the caller branches on. The reference is read-only — it never mutates the manifest. + +## Parameters + +The caller provides these via context before loading: + +- `work_unit` — the epic's work unit name. Always present. +- `proposed_name` — the topic name the user has proposed. Always present. + +After return, the caller reads `result` from conversation memory. Possible values: + +- `collision-active` — name matches an active discovery-map item. Rejection rendered. +- `matches-dismissed` — name matches an entry on the dismissed list. **Informational** — caller pulls before writing. +- `ok` — no conflict. Caller proceeds. + +## A. Normalise to Kebab-Case + +Callers always have Claude generate or extract the proposed name before invoking this reference, so a non-kebab-case `proposed_name` is a Claude-side slip — not a user-facing error. The fix is to self-correct silently, then carry on. + +A kebab-case name is lowercase ASCII letters, digits, and `-`. No leading or trailing `-`, no consecutive `-`, no other characters. See **[casing-conventions.md](casing-conventions.md)** for the canonical rule. + +Test `proposed_name` against this pattern: `^[a-z0-9]+(-[a-z0-9]+)*$`. + +#### If the name matches + +→ Proceed to **B. Read Map and Dismissed List**. + +#### Otherwise + +Re-derive a kebab-case form for `proposed_name` per casing-conventions.md (lowercase, split on spaces/underscores/punctuation, join with single hyphens, strip leading/trailing hyphens). Use the corrected value as `proposed_name` for all subsequent steps. Do not render a rejection or surface the correction to the user — the caller and the user only see the normalised name from this point onward. + +→ Proceed to **B. Read Map and Dismissed List**. + +## B. Read Map and Dismissed List + +Re-run discovery to pick up state changes since the caller's last invocation (writes earlier in the session, prior splits in the same batch): + +```bash +node .claude/skills/workflow-inception-process/scripts/discovery.cjs {work_unit} +``` + +Read: + +- `discovery_map` — list of active topic items. The `name` field of each entry is the case-sensitive map name. +- `dismissed` — array of names previously removed via refinement. + +→ Proceed to **C. Compare Against Active Map**. + +## C. Compare Against Active Map + +Check whether `proposed_name` matches any `name` in `discovery_map` (case-sensitive — kebab-case enforcement in **A** means this is effectively case-insensitive too). + +#### If a match exists + +Set `result = "collision-active"` and render the rejection: + +> *Output the next fenced block as a code block:* + +``` +"{proposed_name}" is already on the map. Pick a different name +or use edit-summary / change-routing on the existing item. +``` + +→ Return to caller. + +#### Otherwise + +→ Proceed to **D. Compare Against Dismissed List**. + +## D. Compare Against Dismissed List + +Check whether `proposed_name` matches any entry in `dismissed` (case-sensitive). + +A dismissed-list match is **not** a rejection. User-explicit spawns (split, elevation, refinement add, direct-entry) bypass the dismissed list — the list only blocks automatic re-adds by analyses. The caller pulls the name from `dismissed` before writing the new item. + +#### If a match exists + +Set `result = "matches-dismissed"`. + +→ Return to caller. + +#### Otherwise + +→ Proceed to **E. Return OK**. + +## E. Return OK + +Set `result = "ok"`. + +→ Return to caller.