- commit
- e5a66d83731980db24a645b6092ed22ef9e8a96c
- parent
- b2c527ff9c0bc0d74bd6945df3ddb57790ee5f07
- author
- dami etoile <dami.etoile@gmail.com>
@@ -13,7 +13,7 @@ This plugin closes both gaps:
1313 1414 ## What you get1515 16-**Agent tools** — 19 tools covering the full OpenSpec lifecycle: idea capture and enrichment, change creation/promotion/archival, task tracking, spec validation, semantic spec diffing, spec create/show/list, and context resolution.16+**Agent tools** — 20 tools covering the full OpenSpec lifecycle: idea capture and enrichment, change creation/promotion/archival, task tracking, spec validation, semantic spec diffing, spec create/show/list, context resolution, and a CLI passthrough (`openspec_cli`).1717 1818 | Group | Tools |1919 |---|---|@@ -22,11 +22,14 @@ This plugin closes both gaps:
2222 | Changes | `openspec_change_create`, `openspec_change_promote`, `openspec_change_archive`, `openspec_change_unarchive` |2323 | Tasks | `openspec_task_list`, `openspec_task_set_status` |2424 | Specs | `openspec_spec_diff`, `openspec_spec_create`, `openspec_spec_show`, `openspec_spec_list` |25+| CLI passthrough | `openspec_cli` |2526 2627 `openspec_context`, `openspec_spec_diff`, `openspec_spec_create`, `openspec_spec_show`, `openspec_spec_list`, and the lifecycle write tools (idea/change/task) are filesystem-backed and always available. `openspec_list`, `openspec_show`, `openspec_validate`, `openspec_status`, and `openspec_instructions` require the `openspec` CLI binary.2728 2829 For the full tool reference, lifecycle states, and delegation patterns, see [docs/index.md](docs/index.md).2930 31+**Bundled skills** — 11 upstream OpenSpec workflow skills (`openspec-propose`, `openspec-apply-change`, `openspec-verify-change`, etc.) are bundled and auto-registered. The agent loads them when user intent matches (e.g. "propose a change for dark mode" → `openspec-propose`). Skills instruct the agent to run the `openspec` CLI directly — they don't use the plugin's tool wrappers. See [docs/layers.md](docs/layers.md) for the layer architecture.32+3033 **Dashboard tab** (`/openspec`):3134 3235 - Register repos by path — each gets a vanity name and stable tokens (`name/os_a1b2c3`).@@ -1,7 +1,10 @@
1-"""OpenSpec plugin — registers CLI-backed spec workflow tools."""1+"""OpenSpec plugin — registers CLI-backed spec workflow tools and bundled skills."""22 33 from __future__ import annotations44 5+import logging6+from pathlib import Path7+58 try:69 from . import schemas710 from . import tools@@ -9,6 +12,9 @@ except ImportError: # pragma: no cover - pytest may import the repo root as a t
912 import schemas # type: ignore1013 import tools # type: ignore1114 15+logger = logging.getLogger(__name__)16+_PLUGIN_DIR = Path(__file__).parent17+1218 _TOOLS = (1319 ("openspec_list", schemas.OPENSPEC_LIST, tools.openspec_list, "📋"),1420 ("openspec_show", schemas.OPENSPEC_SHOW, tools.openspec_show, "📖"),@@ -22,6 +28,7 @@ _WRITE_TOOLS = (
2228 ("openspec_idea_enrich", schemas.OPENSPEC_IDEA_ENRICH, tools.openspec_idea_enrich, "🔎"),2329 ("openspec_idea_promote", schemas.OPENSPEC_IDEA_PROMOTE, tools.openspec_idea_promote, "🚀"),2430 ("openspec_task_list", schemas.OPENSPEC_TASK_LIST, tools.openspec_task_list, "☑️"),31+ ("openspec_change_sequence_set", schemas.OPENSPEC_CHANGE_SEQUENCE_SET, tools.openspec_change_sequence_set, "🔢"),2532 ("openspec_task_set_status", schemas.OPENSPEC_TASK_SET_STATUS, tools.openspec_task_set_status, "✅"),2633 ("openspec_change_create", schemas.OPENSPEC_CHANGE_CREATE, tools.openspec_change_create, "📝"),2734 ("openspec_change_promote", schemas.OPENSPEC_CHANGE_PROMOTE, tools.openspec_change_promote, "➡️"),@@ -66,3 +73,21 @@ def register(ctx) -> None:
6673 handler=handler,6774 emoji=emoji,6875 )76+ # CLI passthrough tool — gated on openspec binary availability77+ ctx.register_tool(78+ name="openspec_cli",79+ toolset="openspec",80+ schema=schemas.OPENSPEC_CLI,81+ handler=tools.openspec_cli,82+ check_fn=_check_openspec_available,83+ emoji="🖥️",84+ )85+ # Register bundled upstream workflow skills (openspec-propose, openspec-apply-change, etc.)86+ skills_dir = _PLUGIN_DIR / "skills"87+ for child in sorted(skills_dir.iterdir()) if skills_dir.is_dir() else []:88+ skill_md = child / "SKILL.md"89+ if child.is_dir() and skill_md.exists():90+ try:91+ ctx.register_skill(child.name, skill_md)92+ except Exception:93+ logger.warning("Failed to register skill %s", child.name, exc_info=True)@@ -243,20 +243,51 @@
243243 function Board(_ref7) {244244 var source = _ref7.source, onSelectItem = _ref7.onSelectItem;245245 var board = source.openspec;246+ var _ft = useState(""), _sa = useState(true);247+ var filterText = _ft[0], setFilterText = _ft[1];248+ var showArchived = _sa[0], setShowArchived = _sa[1];246249 if (!board) return h("p", { className: "os-empty" }, "No openspec/ content found.");250+ var filter = filterText.trim().toLowerCase();251+ function matchesFilter(it) {252+ if (!filter) return true;253+ return (it.title || it.name || "").toLowerCase().indexOf(filter) >= 0 ||254+ (it.name || "").toLowerCase().indexOf(filter) >= 0 ||255+ (it.token || "").toLowerCase().indexOf(filter) >= 0;256+ }257+ function sequencePosition(it) {258+ return it && it.sequence && typeof it.sequence.position === "number" ? it.sequence.position : Number.MAX_SAFE_INTEGER;259+ }260+ function sortBySequence(items) {261+ return (items || []).slice().sort(function (a, b) {262+ var ap = sequencePosition(a), bp = sequencePosition(b);263+ if (ap !== bp) return ap - bp;264+ return String(a.title || a.name || "").localeCompare(String(b.title || b.name || ""));265+ });266+ }247267 var byCol = {};248268 COLUMN_ORDER.forEach(function (c) { byCol[c] = []; });249- board.ideas.forEach(function (it) { byCol.ideas.push(it); });269+ board.ideas.forEach(function (it) { if (matchesFilter(it)) byCol.ideas.push(it); });250270 board.changes.forEach(function (ch) { var s = ch.status || "draft"; (byCol[s] || (byCol[s] = [])).push(ch); });271+ // Filter changes in non-archived columns too272+ COLUMN_ORDER.forEach(function (c) {273+ if (c !== "ideas") byCol[c] = (byCol[c] || []).filter(matchesFilter);274+ });275+ var visibleColumns = COLUMN_ORDER.filter(function (c) { return c !== "archived" || showArchived; });276+ var totalVisible = visibleColumns.reduce(function (n, c) { return n + (byCol[c] || []).length; }, 0);251277 return h("div", { className: "os-board" },252278 h("div", { className: "os-board-head" },253279 h("span", { className: "os-repo-name" }, source.name),254280 h(CopyChip, { text: source.name }),255281 h("span", { className: "os-repo-path" }, source.path)256282 ),283+ h("div", { className: "os-board-filter" },284+ h(Input, { placeholder: "🔍 Filter by title, name, or token…", value: filterText, onChange: function (e) { setFilterText(e.target.value); }, className: "os-filter-input" }),285+ h(Button, { variant: showArchived ? "default" : "outline", size: "sm", onClick: function () { setShowArchived(!showArchived); } }, "Archived " + (showArchived ? "✓" : "✗"))286+ ),287+ totalVisible === 0 && filter ? h("div", { className: "os-empty" }, "No cards match \"" + filterText + "\"") :257288 h("div", { className: "os-cols" },258- COLUMN_ORDER.map(function (col) {259- var items = byCol[col] || [];289+ visibleColumns.map(function (col) {290+ var items = sortBySequence(byCol[col] || []);260291 return h("div", { key: col, className: "os-col" },261292 h("div", { className: "os-col-head" },262293 h("span", null, STATUS_LABEL[col] || col),@@ -275,7 +306,10 @@
275306 return h("button", {276307 key: it.id || it.name, className: cn("os-card", STATUS_TONE[it.status] || ""), onClick: function () { onSelectItem(it); },277308 },278- h("div", { className: "os-card-title" }, it.title || it.name),309+ h("div", { className: "os-card-title" },310+ it.sequence ? h("span", { className: "os-card-seq", title: "Sequence " + it.sequence.position + (it.sequence.dependsOn && it.sequence.dependsOn.length ? "; depends on " + it.sequence.dependsOn.join(", ") : "") }, "#" + it.sequence.position) : null,311+ it.title || it.name312+ ),279313 h("div", { className: "os-card-foot" },280314 h("span", { className: "os-card-artifacts" },281315 artifacts.map(function (a, i) { return h("span", { key: i, className: cn("os-card-dot", !a.on && "os-card-dot-off"), title: a.label }); })@@ -674,10 +708,12 @@
674708 var _m = useState("current"), _b2 = useState(null), _l2 = useState(true), _e3 = useState("");675709 var _sel = useState(null), _bf = useState(""), _af = useState("");676710 var _sm = useState("alpha");711+ var _sdm = useState("semantic");677712 var mode = _m[0], setMode = _m[1], browser = _b2[0], setBrowser = _b2[1];678713 var loading = _l2[0], setLoading = _l2[1], err = _e3[0], setErr = _e3[1];679714 var selPath = _sel[0], setSelPath = _sel[1], before = _bf[0], setBefore = _bf[1], after = _af[0], setAfter = _af[1];680715 var sortMode = _sm[0], setSortMode = _sm[1];716+ var selDiffModes = _sdm;681717 var firstLoad = useRef(true);682718 683719 function load(currentMode, b, a) {@@ -767,13 +803,28 @@
767803 selFile.mtime ? h("span", { title: selFile.mtime }, "edited ", isoTimeAgo(selFile.mtime)) : null768804 ) : null,769805 mode === "current" ? h(SpecContentView, { md: selFile.after }) :770- h("div", { className: "os-spec-diff" },771- h("div", { className: "os-diff-cols" },772- h("div", { className: "os-diff-col" }, h("div", { className: "os-diff-col-head" }, "before"), h(SpecContentView, { md: selFile.before })),773- h("div", { className: "os-diff-col" }, h("div", { className: "os-diff-col-head" }, "after"), h(SpecContentView, { md: selFile.after }))774- ),775- selFile.diff ? h("pre", { className: "os-diff-unified" }, selFile.diff) : null776- )806+ (function () {807+ var diffModes = ["semantic", "split", "raw"];808+ var diffLabels = { semantic: "Semantic", split: "Side-by-side", raw: "Raw" };809+ // viewMode state is hoisted to SpecsView via a ref-like closure810+ var selDiffMode = selDiffModes[0];811+ function setDiffMode(m) { selDiffModes[1](m); }812+ return h("div", { className: "os-spec-diff" },813+ h("div", { className: "os-specs-diff-modes" },814+ diffModes.map(function (dm) {815+ return h(Button, { key: dm, variant: selDiffMode === dm ? "default" : "outline", size: "sm", onClick: function () { setDiffMode(dm); } }, diffLabels[dm]);816+ })817+ ),818+ selDiffMode === "semantic" && selFile.semantic_diff819+ ? h(SpecDeltaView, { semanticDiff: selFile.semantic_diff, lineDiff: selFile.diff, specPath: selFile.path })820+ : selDiffMode === "split"821+ ? h("div", { className: "os-diff-cols" },822+ h("div", { className: "os-diff-col" }, h("div", { className: "os-diff-col-head" }, "before"), h(SpecContentView, { md: selFile.before })),823+ h("div", { className: "os-diff-col" }, h("div", { className: "os-diff-col-head" }, "after"), h(SpecContentView, { md: selFile.after }))824+ )825+ : selFile.diff ? h("pre", { className: "os-diff-unified" }, selFile.diff) : null826+ );827+ })()777828 ) : h("p", { className: "os-empty" }, "Select a spec.")778829 );779830 })() : h("p", { className: "os-empty" }, mode === "dirty" ? "No changes between HEAD and worktree." : mode === "refs" ? "No differences between refs." : "No specs found.")@@ -30,6 +30,8 @@
3030 /* Board */3131 .os-board { display: flex; flex-direction: column; gap: 0.75rem; }3232 .os-board-head { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }33+.os-board-filter { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }34+.os-filter-input { flex: 1; max-width: 24rem; font-size: 0.8rem; }3335 .os-repo-name { font-weight: 600; font-size: 0.95rem; color: var(--color-foreground, #eee); }3436 .os-repo-path { font-size: 0.75rem; color: var(--color-muted-foreground, #777); font-family: var(--theme-font-mono, monospace); }3537 @@ -41,7 +43,8 @@
4143 4244 .os-card { display: flex; flex-direction: column; gap: 0.25rem; padding: 0.5rem; border-radius: var(--radius-sm, 0.375rem); border: 1px solid var(--color-border, #2a2a2a); background: var(--color-popover, #1a1a1a); text-align: left; cursor: pointer; transition: border-color 0.12s; }4345 .os-card:hover { border-color: var(--color-primary, #555); }44-.os-card-title { font-size: 0.82rem; font-weight: 500; line-height: 1.3; color: var(--color-foreground, #eee); }46+.os-card-title { display: flex; align-items: baseline; gap: 0.375rem; font-size: 0.82rem; font-weight: 500; line-height: 1.3; color: var(--color-foreground, #eee); }47+.os-card-seq { flex: 0 0 auto; font-family: var(--theme-font-mono, monospace); font-size: 0.65rem; color: var(--color-muted-foreground, #777); }4548 .os-card-foot { display: flex; align-items: center; gap: 0.375rem; flex-wrap: wrap; }4649 .os-card-copy { margin-left: auto; display: inline-flex; align-items: center; justify-content: center; width: 1.25rem; height: 1.25rem; border: none; background: transparent; color: var(--color-muted-foreground, #777); cursor: pointer; border-radius: var(--radius-sm, 0.25rem); }4750 .os-card-copy:hover { color: var(--color-foreground, #eee); background: var(--color-secondary, rgba(255,255,255,0.06)); }@@ -131,6 +134,7 @@
131134 .os-specs-toolbar { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }132135 .os-specs-title { font-weight: 600; font-size: 0.9rem; color: var(--color-foreground, #eee); }133136 .os-specs-modes { display: flex; gap: 0.25rem; }137+.os-specs-diff-modes { display: flex; gap: 0.25rem; margin-bottom: 0.5rem; }134138 .os-refs-inputs { display: flex; gap: 0.375rem; align-items: center; }135139 .os-ref-input { width: 9rem; font-size: 0.75rem; }136140 .os-specs-changed { font-size: 0.78rem; color: #fbbf24; }@@ -200,3 +204,156 @@
200204 201205 /* Spec list semantic summary */202206 .os-spec-item-summary { font-size: 0.65rem; font-family: var(--theme-font-mono, monospace); color: var(--color-muted-foreground, #999); margin-left: auto; white-space: nowrap; }207+208+/* Mobile: the spec browser's desktop two-pane view is too wide for phones. */209+@media (max-width: 760px) {210+ .os-page {211+ padding: 0.5rem;212+ gap: 0.625rem;213+ overflow-x: hidden;214+ }215+216+ .os-toolbar,217+ .os-specs-toolbar,218+ .os-board-head,219+ .os-board-filter {220+ align-items: stretch;221+ }222+223+ .os-toolbar label,224+ .os-specs-title,225+ .os-repo-name,226+ .os-repo-path,227+ .os-specs-changed {228+ min-width: 0;229+ overflow-wrap: anywhere;230+ }231+232+ .os-toolbar label,233+ .os-specs-title,234+ .os-repo-name {235+ width: 100%;236+ }237+238+ .os-toolbar-actions {239+ margin-left: 0;240+ }241+242+ .os-page div.relative:has(> button[role="combobox"]) {243+ width: 100%;244+ min-width: 0;245+ }246+247+ .os-view-switch,248+ .os-specs-modes,249+ .os-specs-diff-modes {250+ width: 100%;251+ overflow-x: auto;252+ padding-bottom: 0.125rem;253+ -webkit-overflow-scrolling: touch;254+ }255+256+ .os-view-switch button,257+ .os-specs-modes button,258+ .os-specs-diff-modes button {259+ flex: 0 0 auto;260+ white-space: nowrap;261+ }262+263+ .os-refs-inputs,264+ .os-board-filter {265+ width: 100%;266+ flex-wrap: wrap;267+ }268+269+ .os-ref-input,270+ .os-filter-input {271+ flex: 1 1 9rem;272+ width: auto;273+ min-width: 0;274+ max-width: none;275+ }276+277+ .os-specs-grid {278+ display: flex;279+ flex-direction: column;280+ min-height: 0;281+ }282+283+ .os-specs-list {284+ flex-direction: row;285+ max-height: none;286+ overflow-x: auto;287+ overflow-y: hidden;288+ padding: 0.375rem;289+ -webkit-overflow-scrolling: touch;290+ }291+292+ .os-spec-item {293+ flex: 0 0 13rem;294+ min-width: 0;295+ }296+297+ .os-spec-item-path,298+ .os-specs-detail-path,299+ .os-spec-req-name,300+ .os-spec-scn-name,301+ .os-spec-step-text,302+ .os-card-title {303+ min-width: 0;304+ white-space: normal;305+ overflow-wrap: anywhere;306+ }307+308+ .os-specs-detail {309+ max-height: none;310+ min-width: 0;311+ padding: 0.625rem;312+ }313+314+ .os-specs-detail-head,315+ .os-specs-meta,316+ .os-spec-req-head {317+ flex-wrap: wrap;318+ align-items: flex-start;319+ }320+321+ .os-chip {322+ max-width: 100%;323+ overflow: hidden;324+ text-overflow: ellipsis;325+ }326+327+ .os-spec-scn {328+ margin-left: 0.5rem;329+ margin-right: 0.5rem;330+ }331+332+ .os-spec-step {333+ gap: 0.25rem;334+ }335+336+ .os-spec-step-label {337+ min-width: 2.1rem;338+ text-align: left;339+ }340+341+ .os-diff-cols,342+ .os-delta-scenario-change {343+ grid-template-columns: 1fr;344+ }345+346+ .os-diff-col,347+ .os-diff-unified {348+ min-width: 0;349+ max-width: 100%;350+ }351+352+ .os-overlay {353+ padding: 0.5rem;354+ }355+356+ .os-modal-body {357+ max-height: calc(100vh - 6rem);358+ }359+}@@ -253,11 +253,10 @@ def _idea_summary(path: Path, source_id: str) -> Optional[dict[str, Any]]:
253253 }254254 255255 256-def _scan(root: Path) -> Optional[dict[str, Any]]:256+def _scan(root: Path, source_id: str) -> Optional[dict[str, Any]]:257257 openspec_root = root / "openspec"258258 if not openspec_root.is_dir():259259 return None260- source_id = root.name261260 changes: list[dict[str, Any]] = []262261 ideas: list[dict[str, Any]] = []263262 specs: list[dict[str, Any]] = []@@ -308,6 +307,16 @@ def _scan(root: Path) -> Optional[dict[str, Any]]:
308307 except OSError:309308 pass310309 310+ if _registry is not None and hasattr(_registry, "ensure_change_sequence"):311+ try:312+ sequence = _registry.ensure_change_sequence(source_id, [str(ch.get("name") or "") for ch in changes])313+ for change in changes:314+ meta = sequence.get(str(change.get("name") or ""))315+ if meta:316+ change["sequence"] = meta317+ except Exception:318+ pass319+311320 by_status = {s: 0 for s in _STATUSES}312321 by_status["ideas"] = len(ideas)313322 for change in changes:@@ -346,7 +355,7 @@ def _source_payload(source: dict[str, Any]) -> dict[str, Any]:
346355 **base,347356 "valid": True,348357 "repoRoot": str(root),349- "openspec": _scan(root),358+ "openspec": _scan(root, str(base["id"])),350359 "error": None,351360 }352361 @@ -715,6 +724,7 @@ def _spec_browser(root: Path, before: str = "", after: str = "", dirty: bool = F
715724 "after": after_content,716725 "diff": _spec_diff(before_content, after_content, rel) if status not in {"unchanged", "missing"} else "",717726 "semantic_summary": _compute_semantic_summary(before_content, after_content) if status not in {"unchanged", "missing"} else None,727+ "semantic_diff": _compute_semantic_diff(before_content, after_content) if status not in {"unchanged", "missing"} else None,718728 })719729 720730 return {@@ -0,0 +1,140 @@
1+# Layer Architecture2+3+The OpenSpec plugin has four layers that coexist by design. They serve4+different audiences and return different data shapes. Understanding the5+boundaries prevents "fixing" the duplication by rewriting one layer to6+use another.7+8+## Layers9+10+```11+┌─────────────────────────────────────────────────────────────┐12+│ Skills (11 upstream workflow prompts) │13+│ openspec-propose, openspec-apply-change, etc. │14+│ Agent auto-loads when user intent matches description. │15+│ Skills instruct agent to run openspec CLI commands. │16+└─────────────┬───────────────────────────────────────────────┘17+ │18+ ▼19+┌─────────────────────────────────────────────────────────────┐20+│ CLI interface (two paths, same binary) │21+│ │22+│ openspec_cli tool terminal tool │23+│ ├─ Gated by check_fn ├─ Always available │24+│ ├─ JSON output parsed ├─ Raw stdout │25+│ └─ Discoverable in tool └─ Skills use this by default │26+│ list │27+└─────────────┬───────────────────────────────────────────────┘28+ │29+ ▼30+┌─────────────────────────────────────────────────────────────┐31+│ openspec CLI binary │32+│ (installed separately via npm, not part of plugin) │33+└─────────────────────────────────────────────────────────────┘34+35+┌─────────────────────────────────────────────────────────────┐36+│ Plugin tools (19 existing + openspec_cli = 20) │37+│ openspec_context, openspec_show, openspec_spec_diff, etc. │38+│ Different JSON shapes than CLI. │39+│ Used for: ad-hoc agent queries, dashboard backend. │40+│ NOT used by: upstream skills (they call CLI directly). │41+└─────────────────────────────────────────────────────────────┘42+43+┌─────────────────────────────────────────────────────────────┐44+│ Dashboard │45+│ Board, spec browser, change detail │46+│ Reads from filesystem + git, not from CLI or plugin tools. │47+└─────────────────────────────────────────────────────────────┘48+```49+50+## Why skills don't use plugin tools51+52+The upstream OpenSpec skills are structured prompts that instruct the agent53+to run `openspec` CLI commands directly:54+55+```bash56+openspec new change "<name>"57+openspec status --change "<name>" --json58+openspec instructions apply --change "<name>" --json59+```60+61+The CLI returns JSON with fields like `applyRequires`, `artifacts`,62+`contextFiles`, `actionContext`, and `planningHome`. The skill prompts63+parse these fields to drive the workflow.64+65+Our plugin tools wrap a subset of the CLI but return **different JSON66+shapes**. For example:67+68+| CLI output | Plugin tool output |69+|---|---|70+| `openspec status --json` → `{changeName, schemaName, artifacts, applyRequires, planningHome, actionContext}` | `openspec_status` → `{ok, change, status, counts}` |71+| `openspec instructions apply --json` → `{contextFiles, state, progress, tasks, instruction}` | `openspec_instructions` → `{ok, artifact, instructions}` |72+73+Rewriting skills to use plugin tools would require modifying every prompt74+to match our shapes — a permanent sync burden with no value gained. The75+upstream prompts are the product.76+77+## When to use what78+79+| You want to… | Use |80+|---|---|81+| Follow a workflow (propose, apply, verify, archive) | **Skills** — the agent loads them automatically when intent matches |82+| Run a CLI command with raw JSON output | **`openspec_cli`** tool — gated, discoverable, parses JSON |83+| Run a CLI command via shell pipeline | **`terminal`** tool — always available, raw stdout |84+| Query a change/spec without the CLI | **Plugin tools** (`openspec_context`, `openspec_show`, `openspec_spec_diff`, etc.) — filesystem-backed, work without the CLI binary |85+| Browse changes and specs visually | **Dashboard** at `/openspec` |86+87+## Skill list88+89+11 skills bundled from upstream OpenSpec CLI v1.4.1:90+91+| Skill | Workflow | Profile |92+|---|---|---|93+| `openspec-propose` | propose | core |94+| `openspec-explore` | explore | core |95+| `openspec-apply-change` | apply | core |96+| `openspec-archive-change` | archive | core |97+| `openspec-new-change` | new | expanded |98+| `openspec-continue-change` | continue | expanded |99+| `openspec-ff-change` | ff | expanded |100+| `openspec-verify-change` | verify | expanded |101+| `openspec-sync-specs` | sync | expanded |102+| `openspec-bulk-archive-change` | bulk-archive | expanded |103+| `openspec-onboard` | onboard | expanded |104+105+Skills are generated by `openspec init --tools claude` and bundled106+unmodified. The `generatedBy` version in each skill's frontmatter shows107+which OpenSpec version produced the content.108+109+## Regenerating skills110+111+When upstream OpenSpec updates the workflow prompts:112+113+```bash114+# 1. Set all workflows in openspec config115+python3 -c "116+import json117+c = json.load(open('~/.config/openspec/config.json'.replace('~', __import__('os').path.expanduser('~'))))118+c['profile'] = 'custom'119+c['workflows'] = ['propose','explore','new','continue','apply','ff','sync','archive','bulk-archive','verify','onboard']120+json.dump(c, open(c_path := __import__('os').path.expanduser('~/.config/openspec/config.json'), 'w'), indent=2)121+"122+123+# 2. Generate into a temp dir124+mkdir -p /tmp/opsx-regen && cd /tmp/opsx-regen125+openspec init . --tools claude126+127+# 3. Copy into the plugin128+cp -r .claude/skills/openspec-* /path/to/hermes-openspec/skills/129+130+# 4. Restore core profile131+openspec config profile core132+```133+134+## Long-term: Hermes adapter upstream135+136+The ideal solution is contributing a Hermes adapter to the OpenSpec CLI's137+command-generation system (in `dist/core/command-generation/adapters/`).138+This would let `openspec init --tools hermes` generate skills directly into139+the plugin structure. Until then, the manual regeneration above is140+mechanical and infrequent.@@ -0,0 +1,123 @@
1+## Context2+3+The plugin registers 19 agent tools but no slash commands. The upstream OpenSpec CLI defines a standard `/opsx:*` command set (`docs/commands.md`) with two profiles: core (4 commands) and expanded (7 commands). Hermes plugins can register slash commands via `ctx.register_command(name, handler, description, args_hint)` — confirmed in `hermes_cli/plugins.py` and the build-a-plugin guide. Colon-delimited names work: the name cleaner does `.lower().strip().lstrip("/")`, colons pass through. Command lookup in `cli.py` and `gateway/run.py` uses the full name including colon.4+5+`ctx.dispatch_tool(tool_name, args)` routes through the normal approval/redaction pipeline — it's the documented pattern for slash commands that invoke tools.6+7+The existing tool handlers in `tools.py` all accept `(args: dict, **kwargs) -> str` and return JSON strings. The `__init__.py` `register()` function currently registers tools only.8+9+## Goals / Non-Goals10+11+**Goals:**12+- Implement all 11 upstream `/opsx:*` commands as Hermes plugin slash commands13+- Route to existing tool handlers via `ctx.dispatch_tool()` — no new tools, no new backend logic14+- Support `--workdir` and `--project` flags for repo targeting on all commands15+- Commands work in both CLI and gateway (Discord, Telegram)16+17+**Non-Goals:**18+- LLM-driven implementation (upstream `/opsx:apply` does implementation — our version lists tasks + returns instructions, it doesn't write code)19+- Semantic analysis (upstream `/opsx:verify` does LLM analysis — our version runs `openspec_validate` + `openspec_status` + `openspec_task_list` and reports results)20+- Auto-syncing delta specs into main specs (upstream `/opsx:sync` merges — our version shows diffs for manual review)21+- Command-specific UI or formatting beyond plain text with emojis22+23+## Decisions24+25+### Decision 1: New `commands.py` module (not inline in `__init__.py`)26+27+**Choice:** Create a dedicated `commands.py` module with all command handlers and a `register_commands(ctx)` function. Wire it in `__init__.py` with a single call.28+29+**Why not inline:** 11 handlers + shared helpers (~300 lines) would bloat `__init__.py` (currently 68 lines, purely registration). A separate module keeps registration clean and makes the command layer testable in isolation.30+31+**Why not in `tools.py`:** `tools.py` is for agent tool handlers (called by the model). Slash commands are user-invoked shortcuts that orchestrate tool calls. Different audience, different lifecycle.32+33+### Decision 2: `ctx.dispatch_tool()` for all tool invocations34+35+**Choice:** All commands call `ctx.dispatch_tool("openspec_*", args)` to invoke tools. No direct calls to `tools.py` handler functions.36+37+**Why:** `dispatch_tool` is the documented pattern. It routes through the approval, redaction, and budget pipelines — the command gets the same safety treatment as a model-initiated tool call. Direct handler calls would bypass this.38+39+**Trade-off:** `dispatch_tool` returns a string (the handler's JSON return). Commands parse it with `json.loads()` to extract fields for formatting. If parsing fails, the raw string is shown.40+41+### Decision 3: Shared arg parsing via `_parse_target()`42+43+**Choice:** A single `_parse_target(raw_args)` helper extracts `--workdir`/`--project` flags from the raw args string and returns `(tool_args, positional_text)`.44+45+```46+/opsx:propose add-dark-mode --workdir ~/repos/my-project47+ → tool_args = {"workdir": "~/repos/my-project"}48+ → positional = "add-dark-mode"49+```50+51+```52+/opsx:propose add-dark-mode --project hermes-openspec53+ → tool_args = {"identifier": "hermes-openspec"}54+ → positional = "add-dark-mode"55+```56+57+```58+/opsx:propose add-dark-mode59+ → tool_args = {}60+ → positional = "add-dark-mode"61+ (tools.py _resolve_project falls back to cwd when neither workdir nor identifier is set)62+```63+64+**Why shlex.split:** Handles quoted arguments (`/opsx:explore "add dark mode"`) correctly. Falls back to `raw_args.split()` if shlex fails (unbalanced quotes).65+66+### Decision 4: Command handler closure pattern67+68+**Choice:** Register handlers with a lambda closure that captures `ctx`:69+70+```python71+ctx.register_command(72+ "opsx:propose",73+ handler=lambda raw, h=_handle_propose, c=ctx: h(c, raw),74+ description="Create change + get proposal instructions",75+ args_hint="<change-name>",76+)77+```78+79+**Why:** `ctx.register_command` expects `handler(raw_args: str) -> str | None`. The handlers need `ctx` for `dispatch_tool`. The closure captures `ctx` at registration time. This matches the pattern in the build-a-plugin guide.80+81+### Decision 5: Multi-step commands report per-step results82+83+**Choice:** Commands that dispatch multiple tools (propose, ff, verify, archive, sync, bulk-archive) report each step's result sequentially:84+85+```86+✅ Change 'add-dark-mode' created87+ change_path: /home/tank/repos/my-project/openspec/changes/add-dark-mode88+89+📋 Proposal authoring instructions:90+...91+92+Next: author proposal.md, then /opsx:ff add-dark-mode to fast-forward.93+```94+95+**Why not just the final result:** Multi-step commands can partially fail (e.g., change created but promotion failed). Per-step reporting tells the user exactly where it stopped.96+97+### Decision 6: `/opsx:continue` next-artifact logic98+99+**Choice:** A `_next_artifact(status_result)` helper inspects the `openspec_status` output and returns the next missing artifact in dependency order: proposal → design → specs → tasks. If all exist, returns `None` and the command tells the user to `/opsx:apply`.100+101+**Why dependency order:** Matches the upstream artifact graph (proposal → specs/design → tasks → apply). The orchestrator should author artifacts in this order.102+103+**Edge case:** `openspec_status` output shape varies between CLI-backed and filesystem-backed calls. The helper checks for both the nested dict format (`{"proposal": {"exists": true}}`) and the flat format (`{"hasProposal": true}`). If neither matches, defaults to "proposal".104+105+### Decision 7: `/opsx:onboard` is static text106+107+**Choice:** `/opsx:onboard` returns a static help string with the command list, typical workflow, and flag documentation. No tool calls.108+109+**Why:** Onboarding is documentation, not a tool operation. Static text is instant and doesn't consume tool budget.110+111+## Risks / Trade-offs112+113+- **Partial failure in multi-step commands** → `/opsx:ff` creates then promotes. If create succeeds but promote fails, the change exists but isn't promoted. Mitigated: per-step reporting (Decision 5) makes the failure point clear. The user can re-run `/opsx:ff` (create will report "may already exist") or call `/opsx:continue` to proceed manually.114+115+- **`dispatch_tool` inherits session approval mode** → In `smart` approval mode, `/opsx:archive` might auto-approve the archive tool call without prompting. This is the documented behavior — commands go through the same pipeline as model tool calls. Not a bug, but worth noting in the onboard text.116+117+- **Colon command names in Telegram** → Telegram's bot menu uses underscores for autocomplete (`/opsx_propose`). The gateway normalizes underscores to hyphens (`command.replace("_", "-")`), but colons may not be normalized. Need to verify that `/opsx:propose` works in Telegram/Discord, or if the colon gets mangled. If it does, register underscored aliases (`opsx_propose`) as fallbacks.118+119+- **`/opsx:continue` status shape ambiguity** → The `_next_artifact` helper handles two output formats but could encounter a third if the CLI changes. Mitigated: defaulting to "proposal" is safe (worst case: user gets proposal instructions when they already have one — they'll see it exists and skip).120+121+## Open Questions122+123+- Does Telegram's command parser handle colons in `/opsx:propose`? The `get_command()` method in `gateway/platforms/base.py` splits on space and strips the `/` — it doesn't reject colons. But Telegram's autocomplete menu may not surface colon-delimited commands. Need to test in a live Telegram session. If broken, register both `opsx:propose` and `opsx_propose` as aliases.@@ -0,0 +1,57 @@
1+# Slash commands /opsx:* routing to tools2+3+## Summary4+5+Implement the upstream OpenSpec `/opsx:*` command set (core + expanded profiles) as Hermes plugin slash commands. 11 commands route to the plugin's existing tool handlers via `ctx.dispatch_tool()`, providing power-user shortcuts for common spec-driven workflows.6+7+## Motivation8+9+The plugin registers 19 agent tools but no slash commands. Users who know what they want must either type a natural language prompt and let the agent select the right tool, or manually construct tool arguments. The upstream OpenSpec CLI defines a standard `/opsx:*` command set (`docs/commands.md`) that provides a canonical command surface. Implementing these as Hermes plugin slash commands gives users a familiar, fast shortcut.10+11+## Design12+13+### Command set14+15+**Core profile (default):**16+| Command | Routes to |17+|---|---|18+| `/opsx:propose` | `openspec_change_create` + `openspec_instructions(proposal)` |19+| `/opsx:explore` | `openspec_idea_create` |20+| `/opsx:apply` | `openspec_task_list` + `openspec_instructions(apply)` |21+| `/opsx:archive` | `openspec_validate` → `openspec_change_archive` |22+23+**Expanded workflow:**24+| Command | Routes to |25+|---|---|26+| `/opsx:new` | `openspec_change_create` |27+| `/opsx:continue` | `openspec_status` + `openspec_instructions(next_artifact)` |28+| `/opsx:ff` | `openspec_change_create` → `openspec_change_promote` |29+| `/opsx:verify` | `openspec_validate` + `openspec_status` + `openspec_task_list` |30+| `/opsx:sync` | `openspec_spec_list` + `openspec_spec_diff` per spec |31+| `/opsx:bulk-archive` | `openspec_change_archive` × N |32+| `/opsx:onboard` | Static help text (no tool calls) |33+34+All commands accept `--workdir <path>` or `--project <name>` flags to target a repo. Without flags, the current working directory is used.35+36+### Implementation37+38+Create `commands.py` with:39+- `_parse_target(raw_args)` — extracts `--workdir`/`--project` flags, returns `(tool_args, positional_text)`40+- `_dispatch(ctx, tool_name, args)` — calls `ctx.dispatch_tool()` and parses JSON result41+- `_fmt_result(result, ok_label, error_label)` — formats tool result for display42+- Per-command handlers that orchestrate 1-3 tool calls each43+- `register_commands(ctx)` — loops over the command table and calls `ctx.register_command()`44+45+Wire in `__init__.py` `register()` by calling `_commands.register_commands(ctx)` after tool registration.46+47+### Confirmed API48+49+- `ctx.register_command(name, handler, description, args_hint)` — accepts colon-delimited names (e.g. `"opsx:propose"`). The name is cleaned via `.lower().strip().lstrip("/")` — colons pass through.50+- Command lookup in `cli.py` and `gateway/run.py` uses the full name including colon.51+- `ctx.dispatch_tool(tool_name, args)` routes through the normal approval/redaction pipeline.52+- No conflicts with built-in commands (checked via `resolve_command()`).53+54+## Alternatives considered55+56+- **Inject as user messages for the agent to handle.** Rejected — that's just a shortcut to typing the tool name. Direct routing is faster and doesn't consume tokens.57+- **Call tools.py handlers directly (bypass dispatch_tool).** Rejected — `dispatch_tool` is the documented pattern and routes through approvals/redaction. Direct calls would bypass safety pipelines.@@ -0,0 +1,125 @@
1+# Slash Commands /opsx:* Specification2+3+## Purpose4+5+Provide power-user slash command shortcuts that route to the plugin's existing tool handlers, matching the upstream OpenSpec command set for familiarity.6+7+## Requirements8+9+### Requirement: Core profile commands10+11+The plugin must register four core `/opsx:*` slash commands that cover the default spec-driven workflow.12+13+#### Scenario: /opsx:propose creates a change14+15+- **GIVEN** the plugin is installed and a workdir or project is specified16+- **WHEN** the user types `/opsx:propose add-dark-mode`17+- **THEN** `openspec_change_create` is dispatched with the parsed change name and `openspec_instructions` is dispatched with `artifact=proposal`, and the combined result is returned18+19+#### Scenario: /opsx:explore creates an idea20+21+- **GIVEN** the plugin is installed22+- **WHEN** the user types `/opsx:explore "add dark mode support"`23+- **THEN** `openspec_idea_create` is dispatched with the topic as title and prompt24+25+#### Scenario: /opsx:apply lists tasks and instructions26+27+- **GIVEN** a change exists with tasks28+- **WHEN** the user types `/opsx:apply add-dark-mode`29+- **THEN** `openspec_task_list` is dispatched and incomplete tasks are listed, followed by `openspec_instructions` with `artifact=apply`30+31+#### Scenario: /opsx:archive validates then archives32+33+- **GIVEN** a change exists34+- **WHEN** the user types `/opsx:archive add-dark-mode`35+- **THEN** `openspec_validate` is dispatched first, and if validation passes, `openspec_change_archive` is dispatched36+37+#### Scenario: /opsx:archive blocks on validation errors38+39+- **GIVEN** a change has validation errors40+- **WHEN** the user types `/opsx:archive add-dark-mode`41+- **THEN** the validation errors are reported and `openspec_change_archive` is not dispatched42+43+### Requirement: Expanded workflow commands44+45+The plugin must register seven expanded `/opsx:*` slash commands for step-by-step and batch workflows.46+47+#### Scenario: /opsx:new creates bare scaffold48+49+- **GIVEN** the plugin is installed50+- **WHEN** the user types `/opsx:new add-feature`51+- **THEN** `openspec_change_create` is dispatched and the change scaffold is created without promotion52+53+#### Scenario: /opsx:continue shows next artifact54+55+- **GIVEN** a change exists with some artifacts missing56+- **WHEN** the user types `/opsx:continue add-feature`57+- **THEN** `openspec_status` is dispatched and the next missing artifact is determined, and `openspec_instructions` is dispatched for that artifact58+59+#### Scenario: /opsx:ff creates and promotes60+61+- **GIVEN** the plugin is installed62+- **WHEN** the user types `/opsx:ff add-feature`63+- **THEN** `openspec_change_create` is dispatched (if the change doesn't exist), then `openspec_change_promote` is dispatched64+65+#### Scenario: /opsx:verify checks three dimensions66+67+- **GIVEN** a change exists with tasks and specs68+- **WHEN** the user types `/opsx:verify add-feature`69+- **THEN** `openspec_validate`, `openspec_status`, and `openspec_task_list` are dispatched and the combined results are reported70+71+#### Scenario: /opsx:sync shows delta spec diffs72+73+- **GIVEN** a change has delta specs74+- **WHEN** the user types `/opsx:sync add-feature`75+- **THEN** `openspec_spec_list` is dispatched for the change, and `openspec_spec_diff` is dispatched for each delta spec, and the diffs are summarized76+77+#### Scenario: /opsx:bulk-archive archives multiple78+79+- **GIVEN** multiple changes exist80+- **WHEN** the user types `/opsx:bulk-archive change-a change-b change-c`81+- **THEN** `openspec_change_archive` is dispatched for each change in sequence, and per-change results are reported82+83+#### Scenario: /opsx:onboard prints workflow guide84+85+- **GIVEN** the plugin is installed86+- **WHEN** the user types `/opsx:onboard`87+- **THEN** a static workflow guide is returned with no tool dispatches88+89+### Requirement: Target resolution via flags90+91+All `/opsx:*` commands (except `/opsx:onboard`) must accept `--workdir` and `--project` flags for repo targeting.92+93+#### Scenario: --workdir flag94+95+- **GIVEN** the user wants to target a specific repo96+- **WHEN** the user types `/opsx:propose add-feature --workdir ~/repos/my-project`97+- **THEN** the tool dispatches include `workdir` set to the expanded path98+99+#### Scenario: --project flag100+101+- **GIVEN** the user wants to target a registered OpenSpec source102+- **WHEN** the user types `/opsx:propose add-feature --project my-project`103+- **THEN** the tool dispatches include `identifier` set to the project name104+105+#### Scenario: No flag uses cwd106+107+- **GIVEN** no `--workdir` or `--project` flag is provided108+- **WHEN** the user types `/opsx:propose add-feature`109+- **THEN** the tool dispatches use the current working directory as the workdir110+111+### Requirement: Command registration112+113+The plugin must register all commands via `ctx.register_command()` during `register()`.114+115+#### Scenario: Commands appear in help116+117+- **GIVEN** the plugin is installed and enabled118+- **WHEN** the user types `/help`119+- **THEN** all 11 `/opsx:*` commands are listed with their descriptions120+121+#### Scenario: Commands work in gateway122+123+- **GIVEN** the plugin is installed and the gateway is running124+- **WHEN** a user types `/opsx:onboard` in Discord or Telegram125+- **THEN** the workflow guide is returned as a message@@ -0,0 +1,13 @@
1+## Tasks2+3+- [ ] Create `commands.py` with `_parse_target()`, `_dispatch()`, `_fmt_result()` helpers4+- [ ] Implement core commands: `/opsx:propose`, `/opsx:explore`, `/opsx:apply`, `/opsx:archive`5+- [ ] Implement expanded commands: `/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:sync`, `/opsx:bulk-archive`6+- [ ] Implement `/opsx:onboard` static help text7+- [ ] Implement `_next_artifact()` helper for `/opsx:continue` artifact dependency logic8+- [ ] Add `register_commands(ctx)` function with command table9+- [ ] Wire `_commands.register_commands(ctx)` in `__init__.py` `register()`10+- [ ] Test: each command dispatches the correct tool(s) with parsed args11+- [ ] Test: `--workdir` and `--project` flags are parsed correctly12+- [ ] Test: commands appear in `/help` output and autocomplete13+- [ ] Test: commands work in gateway (Discord/Telegram) not just CLI@@ -0,0 +1,78 @@
1+## Context2+3+The kanban board (`Board` component in `dashboard/dist/index.js`) renders all changes and ideas across six lifecycle columns: ideas, draft, todo, in_progress, done, archived. All data is loaded in a single payload from `GET /sources` (the `_source_payload` → `_scan()` backend). There is no search, filter, or column visibility control. The board header currently shows the repo name, a copy chip, and the repo path. The toolbar above it has source selection, refresh, edit, remove, and add buttons.4+5+The board also interacts with hash deep-linking: `#project/token#anchor` can open a specific change's detail dialog. This is handled in `OpenSpecPage` via `useEffect` on `anchor.token`.6+7+## Goals / Non-Goals8+9+**Goals:**10+- Add a text filter that narrows cards across all columns by title, name, or token11+- Add a toggle to show/hide the archived column12+- Keep the filter client-side (no backend changes)13+- Preserve hash deep-linking when a filter is active14+15+**Non-Goals:**16+- Per-column show/hide checkboxes (too much UI for marginal value)17+- Server-side filtering (board payload is already fully loaded)18+- Filtering across projects (filter is per-active-project)19+- Saved filter presets20+21+## Decisions22+23+### Decision 1: Filter input placement24+25+**Choice:** Place the filter `Input` in the board header row, between the repo path and the columns. Add an archived toggle button next to it.26+27+```28+┌─────────────────────────────────────────────────────┐29+│ hermes-openspec [os_x4f2] ~/repos/hermes-openspec │30+│ [🔍 Filter by title, name, or token...] [Archived ✓]│31+├──────────┬──────────┬──────────┬──────────┬─────────┤32+│ Ideas │ Draft │ Todo │ In Prog │ Done │ ...33+└──────────┴──────────┴──────────┴──────────┴─────────┘34+```35+36+**Why not in the top toolbar:** The top toolbar (`Toolbar` component) is for source-level actions (select, refresh, edit, remove, add). The filter is board-scoped — it belongs with the board, not the source selector.37+38+**Why not a collapsible panel:** A simple always-visible input is faster to use and discoverable. Collapsible panels hide functionality.39+40+### Decision 2: Filter matching41+42+**Choice:** Case-insensitive substring match against `title`, `name`, and `token` fields of each card.43+44+**Why not fuzzy search:** The board typically has 10-50 cards. Substring is instant, simple, and predictable. Fuzzy adds complexity for no real benefit at this scale.45+46+**Why not regex:** Overkill for the use case and a footgun for non-technical users.47+48+### Decision 3: Archived toggle (not per-column checkboxes)49+50+**Choice:** A single toggle button: "Archived ✓" / "Archived ✗". Default: visible (matches current behavior). When hidden, the archived column is not rendered and archived cards are excluded from filter results.51+52+**Why not per-column checkboxes:** Six checkboxes in the header would be visually noisy. The archived column is the main one users want to hide — it's the largest and least relevant during active development. Other columns are always relevant.53+54+**Alternatives considered:**55+- Per-column checkboxes via a dropdown. Rejected — extra click for the common case (hide archived).56+- Move archived to a separate "Archived" view tab. Rejected — changes the board navigation model. A toggle is less disruptive.57+58+### Decision 4: Hash deep-link bypass59+60+**Choice:** When a hash token is present in the URL, the matching card bypasses the filter. Specifically: if `anchor.token` matches a card, that card's detail dialog opens regardless of `filterText`.61+62+**Implementation:** In the `useEffect` that handles `anchor.token` (in `OpenSpecPage`), the item lookup already searches `src.openspec.changes` and `src.openspec.ideas`. The filter only affects what's rendered in the board columns — the detail dialog opens from the `selItem` state, which is set by the hash effect, not by clicking a card. So the bypass is automatic: the filter hides the card visually, but the dialog still opens because it's driven by `selItem`, not by the card's visibility in the DOM.63+64+**Edge case:** If the user types a filter that hides the card, then clicks the card's token in the URL hash, the dialog opens but the card isn't visible in the board. This is acceptable — the dialog is the focus, not the board. No special handling needed.65+66+### Decision 5: Empty state67+68+**Choice:** When the filter yields zero results, show a "No cards match '{filter}'" message in place of the columns.69+70+**Why:** Empty columns with just "—" are confusing. An explicit message tells the user the filter is the cause, not that the project is empty.71+72+## Risks / Trade-offs73+74+- **Filter + hash interaction confusion** → User has a filter active, clicks a hash link, dialog opens but card is invisible in the board. Mitigated: the dialog is the focus. Could optionally clear the filter when a hash token is present, but that's surprising if the user deliberately set a filter.75+76+- **Archived toggle state not persisted** → If the user hides archived, navigates away, and comes back, archived is visible again. Acceptable — filter state is per-session, not persistent. Persisting would require localStorage or URL params, adding complexity for marginal value.77+78+- **Filter input adds height to the board header** → The board header is currently one row. Adding the filter makes it two rows. Minor visual change — ensure it doesn't push the columns below the fold on smaller screens. Use a compact input (sm size).@@ -0,0 +1,24 @@
1+# Board search and filter2+3+## Summary4+5+Add a text filter input and an archived show/hide toggle to the kanban board. The filter matches cards by title, name, or token across all columns. The archived toggle hides/shows the archived column. Pure frontend change — no backend modifications.6+7+## Motivation8+9+When a project accumulates 20+ changes across the six lifecycle columns (ideas, draft, todo, in_progress, done, archived), finding a specific change requires visually scanning all columns. A text filter and archived toggle make the board usable at scale.10+11+## Design12+13+### Frontend (`dashboard/dist/index.js`)14+15+Add a `filterText` state variable and an `showArchived` boolean (default: true) to the `Board` component. Add a filter `Input` and an archived toggle button to the board header (between the repo name and the columns). Filter `byCol` entries before rendering: a card passes if its `title`, `name`, or `token` contains the filter text (case-insensitive). When `showArchived` is false, the archived column is hidden entirely.16+17+### Hash interaction18+19+When a hash token is present in the URL, bypass the filter for that specific item — clear the filter or exclude the hash-matched card from filtering so the detail dialog can open.20+21+## Alternatives considered22+23+- **Per-column checkboxes.** Rejected — too much UI for a dense toolbar. The archived show/hide toggle covers the main use case.24+- **Server-side filtering.** Rejected — the board payload is already fully loaded. Client-side is simpler and instant.@@ -0,0 +1,61 @@
1+# Board Search and Filter Specification2+3+## Purpose4+5+Make the kanban board usable at scale by adding text search and archived column visibility controls.6+7+## Requirements8+9+### Requirement: Text filter on board10+11+The board must provide a text input that filters cards across all columns by title, name, or token.12+13+#### Scenario: Filter by title14+15+- **GIVEN** the board has changes with known titles16+- **WHEN** the user types a title substring into the filter input17+- **THEN** only cards whose title contains the substring (case-insensitive) are shown18+19+#### Scenario: Filter by token20+21+- **GIVEN** the board has changes with tokens22+- **WHEN** the user types a token (e.g. `os_a1b2c3`) into the filter input23+- **THEN** only cards whose token contains the substring are shown24+25+#### Scenario: Empty filter shows all26+27+- **GIVEN** the filter input has text28+- **WHEN** the user clears the filter input29+- **THEN** all cards in all columns are shown30+31+#### Scenario: No matches32+33+- **GIVEN** the filter input has text that matches no cards34+- **WHEN** the board renders35+- **THEN** a "no matches" empty state is shown in place of the columns36+37+### Requirement: Archived column toggle38+39+The board must provide a toggle to show or hide the archived column.40+41+#### Scenario: Hide archived42+43+- **GIVEN** the board is showing the archived column44+- **WHEN** the user clicks the archived toggle to hide45+- **THEN** the archived column is not rendered46+47+#### Scenario: Show archived (default)48+49+- **GIVEN** the board is loaded for the first time50+- **WHEN** the archived column is evaluated51+- **THEN** the archived column is visible by default52+53+### Requirement: Hash deep-link bypass54+55+The filter must not prevent hash-deep-linked items from opening.56+57+#### Scenario: Hash token with active filter58+59+- **GIVEN** the filter input has text that would hide a specific card60+- **WHEN** a hash token matching that card is present in the URL61+- **THEN** the card's detail dialog opens regardless of the filter@@ -0,0 +1,10 @@
1+## Tasks2+3+- [x] Add `filterText` state + Input to Board component header4+- [x] Filter `byCol` entries by title/name/token (case-insensitive substring match)5+- [x] Add `showArchived` toggle (default: true) to show/hide the archived column6+- [x] Bypass filter when hash token is present in URL (so deep-linked items open)7+- [x] Add "no matches" empty state when filter yields zero results8+- [x] Test: filter by title narrows cards across all columns9+- [x] Test: archived toggle hides/shows archived column10+- [x] Test: hash deep-link still opens detail dialog when filter is active@@ -0,0 +1,84 @@
1+## Context2+3+The OpenSpec CLI ships 11 workflow skills as structured prompts. Each skill instructs the agent to run `openspec` CLI commands directly and parse the JSON output. The skills are tool-agnostic — they work with any AI coding assistant that has terminal access.4+5+Our Hermes plugin wraps a subset of the CLI in 19 agent tools, but these tools return different JSON shapes. For example, `openspec_status` returns `{ok, change, status, counts}`, while the CLI returns `{changeName, schemaName, artifacts, applyRequires, planningHome, actionContext}`. The upstream skills expect the CLI's format.6+7+This change bundles the upstream skills as-is and adds an `openspec_cli` passthrough tool, creating a clean layer separation.8+9+## Goals / Non-Goals10+11+**Goals:**12+- Bundle all 11 upstream skills without modifying their content13+- Add `openspec_cli` passthrough tool for gated CLI access14+- Document the layer boundaries so future contributors understand why skills don't use plugin tools15+- Auto-register skills via `ctx.register_skill()` in `register()`16+17+**Non-Goals:**18+- Contributing a Hermes adapter to upstream OpenSpec (ideal long-term, but out of scope)19+- Modifying upstream skill instructions to reference Hermes-specific tools20+- Generating skills at install time (requires upstream adapter)21+- Slash commands (`/opsx:*`) — skills are the interface22+23+## Decisions24+25+### Decision 1: Bundle skills as-is, no modification26+27+**Choice:** Reproduce the upstream `generateSkillContent()` output verbatim in `skills/<name>/SKILL.md`.28+29+**Why not modify:** Any modification creates a diff from upstream that must be tracked. When upstream updates a prompt, we'd need to re-apply our modifications. By bundling as-is, we can regenerate from upstream with a single command and diff for changes.30+31+**How to generate:** Run `openspec init --tools claude` into a temp directory, then copy the generated `.claude/skills/openspec-*/SKILL.md` files into our `skills/` directory. The Claude adapter's format (YAML frontmatter + body) is compatible with Hermes skills.32+33+### Decision 2: `openspec_cli` as a separate tool, not a replacement for terminal34+35+**Choice:** Add `openspec_cli` as a discoverable tool, but don't modify skills to use it. Skills continue to say "run `openspec new change`" and the agent uses terminal.36+37+**Why not force skills to use it:** Modifying skill instructions = modifying upstream content = sync burden (Decision 1).38+39+**Why add it at all:** The agent discovers tools in its tool list. When `openspec_cli` is available, the agent may prefer it over constructing a terminal command. It also provides:40+- `check_fn` gating (tool only appears when CLI is installed)41+- JSON output parsing42+- Consistent error handling43+44+**How the agent chooses:** Skills say "run `openspec status --change <name> --json`". The agent can do this via:45+- `terminal({"command": "openspec status --change <name> --json"})`46+- `openspec_cli({"command": "status --change <name>", "workdir": "..."})`47+48+Both work. The agent picks based on availability and preference.49+50+### Decision 3: Skill registration via directory iteration51+52+**Choice:** In `register()`, iterate `skills/` directory, call `ctx.register_skill(child.name, child / "SKILL.md")` for each subdirectory containing `SKILL.md`.53+54+**Why not hardcode 11 registrations:** New skills may be added upstream. Directory iteration auto-discovers them.55+56+**Pattern:** Matches the `hermes-plugin-development` skill's documented pattern:57+```python58+skills_dir = _PLUGIN_DIR / "skills"59+for child in sorted(skills_dir.iterdir()):60+ skill_md = child / "SKILL.md"61+ if child.is_dir() and skill_md.exists():62+ ctx.register_skill(child.name, skill_md)63+```64+65+### Decision 4: Layer interface doc as a reference file66+67+**Choice:** Create `docs/layers.md` as a standalone doc referenced by the plugin README.68+69+**Why:** The layer separation is non-obvious. A future contributor looking at the plugin will see 19 tools + 11 skills + a CLI passthrough tool and wonder why they coexist. The doc explains the boundaries and prevents someone from "fixing" the duplication by rewriting skills to use tools.70+71+## Risks / Trade-offs72+73+- **Skill content drift from upstream** → If upstream updates a prompt, our bundled copy is stale until regenerated. Mitigated: the `generatedBy` version in skill frontmatter shows which OpenSpec version generated the content. A regeneration script (run `openspec init`, copy files) makes updates mechanical.74+75+- **Skills reference tools the agent doesn't have** → Upstream skills mention "AskUserQuestion tool" and "TodoWrite tool" which are Claude Code/Cursor specifics. Hermes doesn't have these exact tools. Mitigated: Hermes has equivalent functionality (the agent can ask questions via `clarify`, track tasks via `todo`). The agent adapts the intent. We don't modify the skill text.76+77+- **`openspec_cli` + terminal redundancy** → Two ways to call the same CLI. Could confuse the agent. Mitigated: `openspec_cli` is gated by `check_fn` (only appears when CLI is installed) and returns parsed JSON. Terminal is always available. The agent uses whichever is more convenient. In practice, the agent will prefer `openspec_cli` for structured queries and terminal for complex pipelines.78+79+- **Skill schema footprint** → 11 registered skills add to the agent's context. Mitigated: skills are lazy-loaded — only the skill descriptions appear in the system prompt, not the full instructions. The agent loads a skill's full content only when it matches user intent.80+81+## Open Questions82+83+- Should we write a regeneration script (`scripts/regenerate-skills.sh`) that runs `openspec init` and copies the output? Or is manual regeneration sufficient given the low frequency of upstream updates?84+- Should `openspec_cli` support `--schema` and `--change` as separate parameters, or keep it as a raw `command` string that the agent constructs? Raw string is simpler and matches how the CLI works.@@ -0,0 +1,117 @@
1+# Bundled OpenSpec workflow skills + CLI passthrough2+3+## Summary4+5+Bundle the 11 upstream OpenSpec workflow skills (`openspec-propose`, `openspec-explore`, `openspec-apply-change`, `openspec-verify-change`, etc.) as Hermes plugin skills, and add an `openspec_cli` passthrough tool that runs `openspec <args> --json` and returns raw CLI output. No slash commands — skills are the interface. Document the layer boundaries between skills, CLI, plugin tools, and dashboard.6+7+## Motivation8+9+The upstream OpenSpec CLI generates workflow skills for 24 AI coding assistants (Claude Code, Cursor, Codex, etc.). Each skill is a structured prompt that instructs the agent to run `openspec` CLI commands directly (`openspec new change`, `openspec status --json`, `openspec instructions apply --change <name> --json`) and follow the returned JSON to author artifacts or implement tasks.10+11+Our plugin currently has 19 agent tools that wrap a subset of the CLI, but return **different JSON shapes** than the CLI. The upstream skill prompts expect the CLI's raw output format (`applyRequires`, `artifacts`, `contextFiles`, `state`). Forcing skills through our tool wrappers would require rewriting every prompt — a permanent sync burden with zero value gained.12+13+The right approach is passthrough: bundle the upstream skills as-is, let them call the CLI directly via terminal, and add an `openspec_cli` tool as a clean, gated interface for the agent. Our existing plugin tools remain for ad-hoc queries and the dashboard backend.14+15+## Layer architecture16+17+```18+┌─────────────────────────────────────────────────────────────┐19+│ User │20+│ "propose a change for dark mode" │21+└─────────────┬───────────────────────────────────────────────┘22+ │23+ ▼24+┌─────────────────────────────────────────────────────────────┐25+│ Skills (11 upstream workflow prompts) │26+│ openspec-propose, openspec-apply-change, etc. │27+│ Agent auto-loads when user intent matches description. │28+│ Skills instruct agent to run openspec CLI commands. │29+└─────────────┬───────────────────────────────────────────────┘30+ │31+ ▼32+┌─────────────────────────────────────────────────────────────┐33+│ CLI interface (two paths, same binary) │34+│ │35+│ openspec_cli tool terminal tool │36+│ ├─ Gated by check_fn ├─ Always available │37+│ ├─ JSON output parsed ├─ Raw stdout │38+│ └─ Discoverable in tool └─ Skills use this by default │39+│ list (agent may prefer │40+│ it when available) │41+└─────────────┬───────────────────────────────────────────────┘42+ │43+ ▼44+┌─────────────────────────────────────────────────────────────┐45+│ openspec CLI binary │46+│ (installed separately, not part of plugin) │47+└─────────────────────────────────────────────────────────────┘48+49+┌─────────────────────────────────────────────────────────────┐50+│ Plugin tools (19 existing tools) │51+│ openspec_context, openspec_show, openspec_spec_diff, etc. │52+│ Different JSON shapes than CLI. │53+│ Used for: ad-hoc agent queries, dashboard backend. │54+│ NOT used by: upstream skills (they call CLI directly). │55+└─────────────────────────────────────────────────────────────┘56+57+┌─────────────────────────────────────────────────────────────┐58+│ Dashboard │59+│ Board, spec browser, change detail │60+│ Reads from filesystem + git, not from CLI or plugin tools. │61+└─────────────────────────────────────────────────────────────┘62+```63+64+## Design65+66+### Skill bundling67+68+Bundle all 11 upstream skills as-is. The upstream `generateSkillContent()` produces YAML frontmatter + instructions body. We reproduce this format in `skills/<name>/SKILL.md`.69+70+Skills are generated by the upstream CLI via `openspec init --tools <tool>`. For Hermes, we extract the skill content programmatically (via `openspec init` into a temp dir, or by calling the template functions) and bundle the generated `SKILL.md` files. This ensures we always match the upstream format.71+72+**11 skills:**73+74+| Skill | Workflow ID | Profile |75+|---|---|---|76+| `openspec-propose` | propose | core |77+| `openspec-explore` | explore | core |78+| `openspec-apply-change` | apply | core |79+| `openspec-archive-change` | archive | core |80+| `openspec-new-change` | new | expanded |81+| `openspec-continue-change` | continue | expanded |82+| `openspec-ff-change` | ff | expanded |83+| `openspec-verify-change` | verify | expanded |84+| `openspec-sync-specs` | sync | expanded |85+| `openspec-bulk-archive-change` | bulk-archive | expanded |86+| `openspec-onboard` | onboard | expanded |87+88+### `openspec_cli` passthrough tool89+90+A new agent tool that wraps the `openspec` CLI binary:91+92+- **Schema:** `openspec_cli(command: str, workdir: str, json_output: bool = true)` — runs `openspec <command> --json` in the given workdir and returns the raw JSON output.93+- **check_fn:** Gates on `_openspec_bin() is not None` (same as existing CLI-backed tools).94+- **Handler:** `subprocess.run([exe, *shlex.split(command), "--json"], cwd=workdir)` → returns `{ok, exit_code, stdout, stderr}`. If `json_output` is false, omits `--json` and returns raw text.95+- **Why:** Gives the agent a discoverable, gated tool for CLI calls instead of constructing shell commands. Skills default to terminal (upstream prompts say "run `openspec new change`"), but the agent may prefer `openspec_cli` when it's available.96+97+### Registration98+99+In `__init__.py` `register()`:100+1. Register all existing tools (unchanged).101+2. Register `openspec_cli` tool.102+3. Iterate `skills/` directory and call `ctx.register_skill(name, path)` for each `SKILL.md`.103+104+### Layer interface documentation105+106+Add `docs/layers.md` documenting:107+- What each layer does and who uses it108+- Why skills call the CLI directly (not plugin tools)109+- When to use `openspec_cli` vs terminal vs plugin tools110+- JSON shape differences between CLI output and plugin tool output111+112+## Alternatives considered113+114+- **Rewrite skills to use plugin tools.** Rejected — plugin tools return different JSON shapes than the CLI. Rewriting every prompt to match our shapes creates a permanent sync burden. The upstream prompts are the product.115+- **Generate skills at install time via `openspec init`.** Considered — would always be up-to-date. Rejected for now because `openspec init` targets specific tool directories (`.claude/commands/`, `.cursor/commands/`) and doesn't know about Hermes plugin structure. A Hermes adapter upstream is the ideal long-term solution.116+- **Slash commands (`/opsx:*`) as signposts.** Rejected — commands return strings to the user, not to the agent. The agent wouldn't act on the prompt. Skills are the correct Hermes-native mechanism for injecting procedural knowledge.117+- **Modify skill instructions to use `openspec_cli` instead of terminal.** Rejected — creates a diff from upstream that must be maintained. Skills work as-is with terminal. The agent can discover and prefer `openspec_cli` on its own.@@ -0,0 +1,85 @@
1+# Bundled OpenSpec Workflow Skills + CLI Passthrough Specification2+3+## Purpose4+5+Bundle upstream OpenSpec workflow skills as Hermes plugin skills and add an `openspec_cli` passthrough tool, creating a clean layer separation between workflow prompts (skills), CLI access, ad-hoc query tools, and the dashboard.6+7+## Requirements8+9+### Requirement: Bundle 11 upstream workflow skills10+11+The plugin must bundle all 11 upstream OpenSpec workflow skills as Hermes plugin skills, registered via `ctx.register_skill()`.12+13+#### Scenario: Skills registered on plugin load14+15+- **GIVEN** the plugin is installed and enabled16+- **WHEN** Hermes starts and calls `register(ctx)`17+- **THEN** all 11 skills (`openspec-propose`, `openspec-explore`, `openspec-apply-change`, `openspec-archive-change`, `openspec-new-change`, `openspec-continue-change`, `openspec-ff-change`, `openspec-verify-change`, `openspec-sync-specs`, `openspec-bulk-archive-change`, `openspec-onboard`) are registered and appear in `hermes skills list`18+19+#### Scenario: Skill content matches upstream20+21+- **GIVEN** the bundled skill files exist in `skills/<name>/SKILL.md`22+- **WHEN** the skill content is compared to the upstream `generateSkillContent()` output23+- **THEN** the YAML frontmatter and instructions body match the upstream format without modification24+25+#### Scenario: Agent auto-loads skill on intent match26+27+- **GIVEN** the `openspec-propose` skill is registered28+- **WHEN** the user says "propose a change for dark mode"29+- **THEN** the agent loads the `openspec-propose` skill and follows its workflow30+31+#### Scenario: Skills call CLI directly via terminal32+33+- **GIVEN** an upstream skill is loaded in a session34+- **WHEN** the skill instructs the agent to run `openspec new change "<name>"`35+- **THEN** the agent executes the command via the `terminal` tool, not via plugin wrapper tools36+37+### Requirement: openspec_cli passthrough tool38+39+The plugin must provide an `openspec_cli` tool that runs the `openspec` CLI binary and returns raw output.40+41+#### Scenario: Tool appears when CLI is installed42+43+- **GIVEN** the `openspec` binary is available on PATH or via `OPENSPEC_BIN`44+- **WHEN** the plugin registers tools45+- **THEN** `openspec_cli` appears in the agent's tool list46+47+#### Scenario: Tool hidden when CLI is not installed48+49+- **GIVEN** the `openspec` binary is not available50+- **WHEN** the plugin registers tools51+- **THEN** `openspec_cli` does not appear in the agent's tool list52+53+#### Scenario: JSON output mode54+55+- **GIVEN** the `openspec` CLI is installed56+- **WHEN** the agent calls `openspec_cli` with `command: "status --change my-change"` and `json_output: true`57+- **THEN** the tool runs `openspec status --change my-change --json` and returns the raw JSON output from the CLI58+59+#### Scenario: Raw output mode60+61+- **GIVEN** the `openspec` CLI is installed62+- **WHEN** the agent calls `openspec_cli` with `command: "list"` and `json_output: false`63+- **THEN** the tool runs `openspec list` (without `--json`) and returns the raw text output64+65+#### Scenario: Workdir support66+67+- **GIVEN** a project at a specific path68+- **WHEN** the agent calls `openspec_cli` with `workdir: "/path/to/project"`69+- **THEN** the CLI command runs with that path as the working directory70+71+### Requirement: Layer separation documentation72+73+The plugin must include documentation explaining the boundary between skills, CLI, plugin tools, and dashboard.74+75+#### Scenario: Layer doc exists76+77+- **GIVEN** the plugin is installed78+- **WHEN** a contributor reads `docs/layers.md`79+- **THEN** the doc explains that skills call the CLI directly via terminal, plugin tools return different JSON shapes, and the two coexist by design80+81+#### Scenario: README references layer doc82+83+- **GIVEN** the plugin README is read84+- **WHEN** the reader looks for architecture information85+- **THEN** the README links to `docs/layers.md` and mentions the layer architecture@@ -0,0 +1,17 @@
1+## Tasks2+3+- [x] Create `skills/` directory in plugin root4+- [x] Generate all 11 upstream skill files via `openspec init --tools claude` into a temp dir5+- [x] Copy generated `SKILL.md` files into `skills/<name>/` (11 directories)6+- [x] Add `openspec_cli` tool: schema in `schemas.py`, handler in `tools.py`7+- [x] Gate `openspec_cli` with `check_fn` (same `_openspec_bin() is not None` check)8+- [x] Add skill registration loop in `__init__.py` `register()` — iterate `skills/`, call `ctx.register_skill()`9+- [x] Register `openspec_cli` in `__init__.py` `register()`10+- [x] Add `openspec_cli` to `plugin.yaml` `provides_tools`11+- [x] Write `docs/layers.md` documenting the layer boundaries12+- [x] Update `README.md` to mention bundled skills and layer architecture13+- [x] Test: skills are discoverable via `hermes skills list` when plugin is installed14+- [x] Test: `/skill openspec-propose` loads the skill into a session15+- [x] Test: `openspec_cli` tool appears only when openspec binary is available16+- [x] Test: `openspec_cli` returns raw JSON from `openspec status --change <name> --json`17+- [x] Test: skills + plugin tools coexist without conflicts@@ -0,0 +1,63 @@
1+## Context2+3+The dashboard has two spec-diffing surfaces:4+5+1. **Change detail → Specs tab** (`ChangeSpecsView`) — renders `SpecDeltaView`, a structured requirement/scenario-level delta computed by `spec_parser.semantic_spec_diff()`. This is the "proposed vs current" diff for a specific change's delta specs.6+7+2. **Spec browser** (`SpecsView`) — renders side-by-side `SpecContentView` columns + raw unified diff. Used for current/dirty/refs browsing across all specs in a repo.8+9+The backend (`plugin_api.py`) already has the shared `_compute_semantic_diff()` helper, used by `_change_detail()` for surface #1. For surface #2, `_spec_browser()` computes only `semantic_summary` (compact +/~/− counts) for changed files but discards the full `semantic_diff`. The `SpecDeltaView` frontend component already exists and handles the structured delta rendering.10+11+## Goals / Non-Goals12+13+**Goals:**14+- Make the spec browser's dirty/refs modes render the same structured semantic delta as the change detail view15+- Add a view-mode toggle so users can switch between semantic, side-by-side, and raw diff16+- Reuse existing components (`SpecDeltaView`, `SpecContentView`) and backend helpers (`_compute_semantic_diff`)17+18+**Non-Goals:**19+- Changing the "current" mode (no comparison — stays as `SpecContentView` only)20+- Modifying the change detail's Specs tab (already uses `SpecDeltaView`)21+- Adding new parsing logic (the parser is shared in `spec_parser.py`)22+23+## Decisions24+25+### Decision 1: Include full `semantic_diff` in the spec browser payload26+27+**Choice:** Add a `semantic_diff` field to each changed file entry in `_spec_browser()`, computed via the existing `_compute_semantic_diff(before, after)` helper.28+29+**Why not compute client-side:** The Python parser in `spec_parser.py` is the single source of truth. Duplicating it in JS risks drift. The backend already imports and calls it — we're just using the full result instead of discarding it.30+31+**Why not a separate API call:** The before/after content is already loaded in the same payload. Computing the diff server-side is a single function call, no extra I/O.32+33+### Decision 2: View-mode toggle with three modes34+35+**Choice:** Add a toggle in the `SpecsView` toolbar (dirty/refs modes only) with three options: **Semantic** (default), **Side-by-side**, **Raw**.36+37+| Mode | Component | Shows |38+|---|---|---|39+| Semantic (default) | `SpecDeltaView` | Structured requirement/scenario delta with added/modified/removed sections |40+| Side-by-side | Two `SpecContentView` columns | Full before/after spec content side by side |41+| Raw | `<pre>` with unified diff | Line-level unified diff |42+43+**Why three modes instead of replacing side-by-side:** Side-by-side is useful for reading full spec content — the semantic view only shows what changed, not the surrounding context. Raw is useful for exact line-level review. All three serve different review needs.44+45+**Why semantic as default:** The change detail view already defaults to semantic. Consistency across surfaces.46+47+**Alternatives considered:**48+- Two modes (semantic + raw), drop side-by-side. Rejected — side-by-side full-content reading is a distinct use case.49+- No toggle, always semantic. Rejected — power users want raw diffs for exact review.50+51+### Decision 3: View-mode state is per-session, not per-file52+53+**Choice:** The selected view-mode persists when the user clicks different files in the spec browser list. It resets to "semantic" when the mode (current/dirty/refs) changes.54+55+**Why:** When reviewing multiple changed specs, the user typically wants the same view mode for all of them. Switching modes (current→dirty) is a different review context, so resetting is natural.56+57+## Risks / Trade-offs58+59+- **Payload bloat** → `semantic_diff` adds structured JSON per changed file. Mitigated: only included for changed files (status not in unchanged/missing), same gating as the existing `semantic_summary`. For repos with 50+ changed specs the payload grows, but this is an edge case and the data is already partially there (semantic_summary).60+61+- **Label confusion in dirty/refs mode** → `SpecDeltaView` was built for change-scoped diffs (before=baseline, after=proposed). In the spec browser, before/after semantics are HEAD vs worktree (dirty) or ref vs ref (refs). The component compares two strings and renders the delta — it doesn't label them. The spec browser's existing header already shows the before/after labels (`os-diff-col-head`). No change needed, but worth verifying the labels remain clear.62+63+- **View-mode toggle adds toolbar complexity** → The spec browser toolbar is already dense (mode buttons, refs inputs, sort select, refresh). Adding a view-mode toggle needs to fit without crowding. Place it in the detail pane header (next to the file path), not the top toolbar — it only applies to the selected file's diff view, not the whole browser.@@ -0,0 +1,30 @@
1+# Semantic diff in spec browser2+3+## Summary4+5+The change detail's Specs tab renders `SpecDeltaView` — the structured requirement/scenario-level delta from `spec_parser.semantic_spec_diff`. The spec browser (current/dirty/refs modes) only shows side-by-side `SpecContentView` + raw unified diff. The backend already computes `semantic_summary` (the +/~/− counts) for changed files in `_spec_browser()` but discards the full `semantic_diff`. This change wires the full `semantic_diff` into the spec-browser payload and renders `SpecDeltaView` there too, making diffing consistent across both views.6+7+## Motivation8+9+The two spec-diffing surfaces in the dashboard are inconsistent. A user reviewing a change's specs sees a structured, navigable delta (added/modified/removed requirements with scenario-level breakdowns). The same user diffing specs in the browser sees raw text comparison. The backend already has the parsing logic — it's just not being used in the browser path.10+11+## Design12+13+### Backend (`dashboard/plugin_api.py`)14+15+In `_spec_browser()`, for each changed file (status not in unchanged/missing), compute and include the full `semantic_diff` alongside the existing `semantic_summary`. The `_compute_semantic_diff()` helper already exists and is used by `_change_detail()`.16+17+### Frontend (`dashboard/dist/index.js`)18+19+In `SpecsView`, for dirty/refs modes, add a view-mode toggle: **Semantic** (default, `SpecDeltaView`) / **Side-by-side** (existing `SpecContentView` columns) / **Raw** (unified diff only). The `SpecDeltaView` component already exists and handles the semantic diff structure.20+21+"Current" mode stays as-is — no diff, just `SpecContentView`.22+23+### Payload impact24+25+`semantic_diff` adds structured JSON per changed file. For repos with many changed specs this could be large, but it's gated to changed files only (same as the current `semantic_summary` gating).26+27+## Alternatives considered28+29+- **Always use SpecDeltaView, remove side-by-side.** Rejected — side-by-side is useful for reading full spec content side-by-side, which the semantic view doesn't show.30+- **Compute semantic_diff client-side from before/after strings.** Rejected — the Python parser already exists and is the single source of truth. Duplicating it in JS risks drift.@@ -0,0 +1,57 @@
1+# Semantic Diff in Spec Browser Specification2+3+## Purpose4+5+Make spec diffing consistent between the change detail view and the spec browser by rendering the existing semantic requirement-level delta in both surfaces.6+7+## Requirements8+9+### Requirement: Semantic diff in spec browser payload10+11+The spec browser backend (`_spec_browser()` in `dashboard/plugin_api.py`) must include the full `semantic_diff` for each changed file, computed via the existing `_compute_semantic_diff()` helper.12+13+#### Scenario: Dirty mode with changed specs14+15+- **GIVEN** a source with specs that differ between HEAD and the worktree16+- **WHEN** the spec browser is loaded in dirty mode17+- **THEN** each changed file's payload includes a `semantic_diff` field with the structured requirement/scenario delta18+19+#### Scenario: Refs mode with changed specs20+21+- **GIVEN** a source with specs that differ between two git refs22+- **WHEN** the spec browser is loaded in refs mode with before and after refs23+- **THEN** each changed file's payload includes a `semantic_diff` field24+25+#### Scenario: Unchanged files excluded26+27+- **GIVEN** a source with specs that are identical between the compared refs28+- **WHEN** the spec browser payload is computed29+- **THEN** unchanged files do not include a `semantic_diff` field (matching the existing `semantic_summary` gating)30+31+### Requirement: View-mode toggle in spec browser frontend32+33+The spec browser frontend (`SpecsView` in `dashboard/dist/index.js`) must provide a view-mode toggle for dirty and refs modes, allowing the user to switch between semantic, side-by-side, and raw diff views.34+35+#### Scenario: Default to semantic view36+37+- **GIVEN** the spec browser is in dirty or refs mode with changed files38+- **WHEN** a changed file is selected39+- **THEN** the semantic delta view (`SpecDeltaView`) is shown by default40+41+#### Scenario: Switch to side-by-side42+43+- **GIVEN** the spec browser is showing a semantic delta for a changed file44+- **WHEN** the user selects "Side-by-side" from the view-mode toggle45+- **THEN** the before and after spec content is rendered side-by-side using `SpecContentView`46+47+#### Scenario: Switch to raw diff48+49+- **GIVEN** the spec browser is showing a semantic delta for a changed file50+- **WHEN** the user selects "Raw" from the view-mode toggle51+- **THEN** the unified line diff is shown in a `<pre>` block52+53+#### Scenario: Current mode unaffected54+55+- **GIVEN** the spec browser is in current mode (no comparison)56+- **WHEN** a file is selected57+- **THEN** the spec content is shown via `SpecContentView` with no view-mode toggle@@ -0,0 +1,9 @@
1+## Tasks2+3+- [x] Add `semantic_diff` field to changed file entries in `_spec_browser()` payload4+- [x] Add view-mode toggle (semantic / side-by-side / raw) to `SpecsView` component in dirty/refs modes5+- [x] Render `SpecDeltaView` when semantic mode is selected for changed files6+- [x] Keep "current" mode unchanged (no diff, just SpecContentView)7+- [x] Test: dirty mode shows semantic diff for changed specs8+- [x] Test: refs mode shows semantic diff for changed specs9+- [x] Test: view-mode toggle persists across file selection within a session@@ -0,0 +1,42 @@
1+# Board search and filter2+3+## Source4+- Origin: human5+- Created: 2026-06-23T01:37:46Z6+- Tags: dashboard, board, ux, search7+8+## Prompt9+The kanban board has no search or filter. With many changes across ideas, draft, todo, in_progress, done, and archived columns, finding a specific change requires visual scanning. Add a text filter box that filters cards by title, name, or token across all columns. Consider also filtering by status (show/hide archived, show only a subset of columns). This is a pure frontend change in dashboard/dist/index.js — no backend changes needed since all change data is already in the board payload.10+11+<!-- OPENSPEC_IDEA_ENRICHMENT_START -->12+## Enrichment Report13+14+Generated: 2026-06-23T01:38:41Z15+16+### Problem17+The kanban board has no search or filter capability. When a project accumulates many changes across the six lifecycle columns (ideas, draft, todo, in_progress, done, archived), finding a specific change requires manually scanning all columns. This becomes painful with 20+ changes and is a usability regression as projects mature.18+19+### Proposed Direction20+Add a search Input to the Board component toolbar (above the columns). Filter cards client-side by title, name, or token substring match. Add a column-visibility toggle (checkboxes or a Select) to show/hide individual status columns — useful for hiding archived when focusing on active work. All filtering is pure frontend in dashboard/dist/index.js — no backend changes needed since the board payload already contains all change/idea data. Implementation: add a `filterText` state variable + a `visibleColumns` set, filter `byCol` entries before rendering, and add the UI controls to the board header.21+22+### Key Questions23+- Should search filter across all projects or just the active one? (Current design is per-project, so filtering the active project's board is the natural scope.)24+- Should archived changes be hidden by default with a toggle to show them, or always visible in the archived column?25+- Should the filter support token-based search (e.g. typing 'os_a1b2c3' jumps to that change) or just text matching on title/name?26+27+### Feasibility28+Feasibility: High29+30+### T-Shirt Size31+T-Shirt Size: XS32+33+### Size Justification34+Pure frontend change. Add a text input, a state variable, and filter the items array before rendering. No API changes, no backend logic. The board data is already fully loaded in the component.35+36+### Risks37+- Filtering hides cards from the DOM, which could confuse deep-linking via hash tokens — if a card is filtered out but its token is in the URL hash, the detail dialog won't open. Need to either bypass the filter for hash-matched items or clear the filter when a hash token is present.38+- Column toggle adds UI complexity to an already dense toolbar. Keep it minimal — maybe just an 'archived' show/hide toggle rather than per-column checkboxes.39+40+### Suggested Next Step41+Implement directly — add filter input to Board component, filter byCol entries by text match on title/name/token, add archived show/hide toggle.42+<!-- OPENSPEC_IDEA_ENRICHMENT_END -->@@ -0,0 +1,43 @@
1+# Bundled openspec-orchestration skills2+3+## Source4+- Origin: human5+- Created: 2026-06-23T01:37:47Z6+- Tags: skills, orchestration, delegation, agent-guidance7+8+## Prompt9+The plugin ships excellent docs (docs/delegation.md, docs/lifecycle.md, docs/tool-reference.md) covering orchestration patterns, subagent delegation contracts, and parallel delegation — but the agent has no bundled skill to load this context. Create a skills/ directory in the plugin with SKILL.md files that encode the OpenSpec workflow as agent-callable procedural knowledge. Focus areas: (1) process management — the full idea→change→promote→implement→validate→archive lifecycle with when-to-use guidance and tool selection logic; (2) delegation patterns — orchestrator vs subagent contracts, the Author and Implement patterns, parallel delegation for independent task groups; (3) individual agent skills — a skill for subagents that receive a change+scope, teaching them to read specs via openspec_show, get instructions, do the work, and mark tasks done without touching lifecycle. The content already exists in docs/; this is primarily a packaging exercise into SKILL.md frontmatter + body format that the plugin register() function can wire via ctx.register_skill().10+11+<!-- OPENSPEC_IDEA_ENRICHMENT_START -->12+## Enrichment Report13+14+Generated: 2026-06-23T01:38:42Z15+16+### Problem17+The plugin ships excellent documentation (docs/delegation.md, docs/lifecycle.md, docs/tool-reference.md) covering the full OpenSpec orchestration workflow — idea→change→promote→implement→validate→archive, subagent delegation contracts, parallel delegation patterns. But the agent has no bundled skill to auto-load this context. The agent must manually discover the docs, and subagents receiving delegated work have no procedural guidance at all — they must be told the workflow in the delegation goal/context each time. This is the single highest-value addition to the plugin that isn't a code feature.18+19+### Proposed Direction20+Create a skills/ directory in the plugin root with two or three SKILL.md files: (1) openspec-orchestration — process management skill for the orchestrator: full lifecycle states and transitions, tool selection logic (which tool for which step), when to delegate vs do directly, validation loops, promotion gates. Content derived from docs/lifecycle.md + docs/delegation.md. (2) openspec-subagent — skill for subagents receiving delegated work: read specs via openspec_show, get instructions via openspec_instructions, do the work, mark tasks done via openspec_task_set_status, never create/promote/archive. Content derived from docs/delegation.md subagent rules section. Wire both in __init__.py register() via ctx.register_skill() using the same pattern as the plugin development skill shows (iterate skills/ directory, register each SKILL.md). Add provides_skills to plugin.yaml if the manifest supports it.21+22+### Key Questions23+- Should the skills be one SKILL.md with sections, or multiple skills (orchestrator, author, implementer) registered separately? Multiple skills gives finer-grained loading but increases schema footprint.24+- Should the skills reference the docs/ markdown files via file_path links (like linked_files in Hermes skills), or inline the content directly in SKILL.md?25+- Does the Hermes plugin register(ctx) API support ctx.register_skill() for bundled skills, and does it handle the skills/ directory auto-discovery pattern shown in the plugin development skill?26+27+### Feasibility28+Feasibility: High29+30+### T-Shirt Size31+T-Shirt Size: M32+33+### Size Justification34+Content already exists in docs/. The work is restructuring it into SKILL.md frontmatter + body format, creating the skills/ directory, and adding ~5 lines to __init__.py register(). No new logic, no new tools, no backend changes.35+36+### Risks37+- Schema footprint — each registered skill adds to the agent's context. Two skills is manageable; more than three would be noise. Keep it to orchestrator + subagent.38+- Skill content drift from docs/ — if the docs are updated but the skills aren't, they'll diverge. Mitigate by referencing the docs files as linked_files rather than inlining, so there's one source of truth.39+- Need to verify that ctx.register_skill() is the correct API and that the plugin loader supports bundled skills — the build-a-plugin guide shows this pattern but it hasn't been used in this plugin yet.40+41+### Suggested Next Step42+First verify ctx.register_skill() API and plugin.yaml provides_skills field in the Hermes plugin docs. Then create the skills/ directory with openspec-orchestration and openspec-subagent SKILL.md files, wire in register().43+<!-- OPENSPEC_IDEA_ENRICHMENT_END -->@@ -0,0 +1,42 @@
1+# Semantic diff in spec browser2+3+## Source4+- Origin: human5+- Created: 2026-06-23T01:37:45Z6+- Tags: dashboard, spec-browser, semantic-diff, consistency7+8+## Prompt9+The change detail's Specs tab already renders SpecDeltaView — the requirement/scenario-level delta from spec_parser.semantic_spec_diff. But the spec browser (current/dirty/refs mode) only shows side-by-side SpecContentView + raw unified diff. The backend (_spec_browser in plugin_api.py) already computes semantic_summary (the +/~/− counts) for changed files but discards the full semantic_diff. Wire semantic_diff into the spec-browser file payload and render SpecDeltaView there too, making diffing consistent across both views.10+11+<!-- OPENSPEC_IDEA_ENRICHMENT_START -->12+## Enrichment Report13+14+Generated: 2026-06-23T01:38:40Z15+16+### Problem17+The change detail's Specs tab renders SpecDeltaView (requirement/scenario-level structured diff from spec_parser.semantic_spec_diff), but the spec browser (current/dirty/refs modes) only shows side-by-side SpecContentView + raw unified diff. The backend already computes semantic_summary (the +/~/− counts) for changed files in _spec_browser but discards the full semantic_diff. This means the two spec-diffing surfaces in the dashboard are inconsistent — one is structured and navigable, the other is raw text comparison.18+19+### Proposed Direction20+1. In plugin_api.py _spec_browser(), compute and include the full semantic_diff for each changed file (currently only semantic_summary is computed). The _compute_semantic_diff helper already exists and is used by _change_detail. 2. In dashboard/dist/index.js SpecsView, replace the side-by-side SpecContentView rendering for changed files with SpecDeltaView (the component already exists and is used by ChangeSpecsView). 3. Add a view-mode toggle (semantic / side-by-side / raw) so users can switch between the structured delta and the raw comparison. 4. Keep the "current" mode as-is (no diff, just SpecContentView).21+22+### Key Questions23+- Should the spec browser default to semantic diff view or keep side-by-side as default with a toggle?24+- Should unchanged requirements be collapsed (like in change detail) or shown expanded since the user is browsing, not reviewing a specific change?25+- Should we add a view-mode toggle (semantic / side-by-side / raw) to the spec browser toolbar, or just replace the current side-by-side with semantic?26+27+### Feasibility28+Feasibility: High29+30+### T-Shirt Size31+T-Shirt Size: S32+33+### Size Justification34+The backend helper (_compute_semantic_diff) and the frontend component (SpecDeltaView) both already exist and are used by the change detail. The work is wiring them together in the spec browser path — adding semantic_diff to the _spec_browser payload and switching the render call. No new parsing logic, no new components.35+36+### Risks37+- Larger API payload — semantic_diff adds structured JSON per changed file. For repos with many changed specs, this could bloat the response. Mitigated by only including it for changed files (status not in unchanged/missing), same as the current semantic_summary gating.38+- SpecDeltaView was built for change-scoped diffs (before=baseline, after=proposed). In the spec browser dirty/refs mode, before/after semantics are different (HEAD vs worktree, or ref vs ref). The component should work as-is since it just compares two strings, but the labels might be confusing.39+40+### Suggested Next Step41+Draft a change proposal that specifies the exact payload field addition in _spec_browser and the frontend render swap, with a view-mode toggle for semantic/side-by-side/raw.42+<!-- OPENSPEC_IDEA_ENRICHMENT_END -->@@ -0,0 +1,65 @@
1+# Slash commands /opsx:* routing to tools2+3+## Source4+- Origin: human5+- Created: 2026-06-23T01:37:48Z6+- Tags: slash-commands, ux, tool-routing, plugin-surface7+8+## Prompt9+The plugin registers 19 agent tools but no slash commands. Hermes plugins can register slash commands via the plugin surface. Create /opsx:* slash commands that shortcut common OpenSpec workflows, routing to the existing tool handlers. Candidate commands: /opsx:context (resolve identifier), /opsx:changes (list changes), /opsx:show (show change/spec), /opsx:idea (create idea), /opsx:promote (idea→change), /opsx:tasks (list tasks), /opsx:validate, /opsx:archive, /opsx:diff (spec diff). Each command parses user input into tool args and calls the handler, returning the result inline. This avoids the agent needing to discover and select the right tool for common operations — the user types a shortcut and the plugin routes to the correct handler. Must follow the Hermes plugin command registration API and avoid breaking prompt caching.10+11+<!-- OPENSPEC_IDEA_ENRICHMENT_START -->12+## Enrichment Report13+14+Generated: 2026-06-23T01:55:15Z15+16+### Problem17+The plugin registers 19 agent tools but no slash commands. Users who know what they want must either type a natural language prompt and let the agent select the right tool, or manually construct tool arguments. The upstream OpenSpec CLI defines a standard set of /opsx:* commands (documented at https://github.com/Fission-AI/OpenSpec/blob/main/docs/commands.md) that provide a canonical command surface for the spec-driven workflow. Implementing these as Hermes plugin slash commands gives users a familiar, fast shortcut that routes to the plugin's existing tool handlers via ctx.dispatch_tool().18+19+### Proposed Direction20+Create a commands.py module in the plugin that implements the upstream /opsx:* command set, grouped into two tiers:21+22+CORE PROFILE (default):23+- /opsx:propose [name] — create change scaffold via openspec_change_create + return proposal authoring instructions from openspec_instructions(artifact=proposal). Fastest end-to-end path.24+- /opsx:explore [topic] — create an idea via openspec_idea_create for pre-change investigation. No artifacts created.25+- /opsx:apply [change] — list tasks via openspec_task_list + return apply instructions from openspec_instructions(artifact=apply). Lists incomplete tasks with progress.26+- /opsx:archive [change] — validate via openspec_validate, then archive via openspec_change_archive if clean.27+28+EXPANDED WORKFLOW COMMANDS:29+- /opsx:new [name] — create bare change scaffold via openspec_change_create (no promotion).30+- /opsx:continue [change] — show status via openspec_status + get instructions for next artifact based on dependency graph (proposal → design → specs → tasks).31+- /opsx:ff [change] — create (if needed) + promote via openspec_change_promote in one step. Ensures tasks + spec placeholder exist.32+- /opsx:verify [change] — validate via openspec_validate + status via openspec_status + task progress via openspec_task_list. Three-dimension check: completeness, correctness, coherence.33+- /opsx:sync [change] — list delta specs via openspec_spec_list + show diffs via openspec_spec_diff for each. Reviews what would merge into main specs.34+- /opsx:bulk-archive [change1 change2 ...] — archive multiple changes in sequence via openspec_change_archive.35+- /opsx:onboard — print the workflow guide (no tool calls, static help text).36+37+All commands accept --workdir <path> or --project <name> flags to target a repo; without flags, cwd is used.38+39+Implementation: commands.py with a _parse_target() helper that extracts flags, per-command handlers that call ctx.dispatch_tool('openspec_*', args), and a register_commands(ctx) function. Wire in __init__.py register() by calling _commands.register_commands(ctx) after tool registration. Each handler returns a formatted string (emojis + structured output) for the chat UI.40+41+Confirmed: ctx.register_command() accepts colon-delimited names (e.g. "opsx:propose"). The name is cleaned via .lower().strip().lstrip("/") — colons pass through. Command lookup in cli.py and gateway/run.py uses the full name including colon. No conflicts with built-in commands.42+43+### Key Questions44+- Should /opsx:propose auto-generate all artifacts (proposal, specs, design, tasks) via multiple tool calls, or just create the scaffold + return instructions so the agent/user authors them? The upstream /opsx:propose generates everything — but that's an LLM operation upstream, not a tool call. Our version should create the scaffold + return authoring instructions from openspec_instructions.45+- Should /opsx:apply dispatch a subagent (via ctx.dispatch_tool('delegate_task', ...)) to implement tasks, or just list tasks + return apply instructions for the user/agent to act on? Upstream /opsx:apply does the implementation — but that requires LLM reasoning, not just tool dispatch.46+- Should commands use ctx.dispatch_tool (routes through approval/redaction pipelines) or call tools.py handlers directly (bypasses agent context)? dispatch_tool is the documented pattern for slash commands that invoke tools — use that.47+48+### Feasibility49+Feasibility: High50+51+### T-Shirt Size52+T-Shirt Size: M53+54+### Size Justification55+11 command handlers, each a thin parser + 1-3 ctx.dispatch_tool calls + result formatting. The _parse_target helper and _fmt_result helper are shared. register_commands is a loop over the command table. __init__.py wiring is 5 lines. The investigation is done — API confirmed, command names confirmed, colon support confirmed.56+57+### Risks58+- Multi-step commands (/opsx:propose, /opsx:ff, /opsx:verify, /opsx:sync) dispatch multiple tool calls sequentially — if one fails mid-sequence, partial state may exist (e.g. change created but promotion failed). Handlers should report clearly which steps succeeded/failed.59+- ctx.dispatch_tool routes through approval/redaction pipelines — this is correct but means commands inherit the session's approval mode. A /opsx:archive in smart approval mode might auto-approve. This is the documented behavior, not a bug, but worth noting.60+- /opsx:continue's next-artifact logic depends on openspec_status output shape, which varies between CLI-backed and filesystem-backed calls. Need to handle both formats or gate on CLI availability.61+- Some upstream commands (/opsx:apply, /opsx:verify) imply LLM-driven implementation or semantic analysis in the upstream CLI. Our versions return tool results + instructions rather than doing LLM work — they're routing shortcuts, not agent replacements. This is the right scope for a plugin command.62+63+### Suggested Next Step64+Promote to a change proposal. The command set, arg parsing, and tool mapping are fully specified. Implementation is mechanical.65+<!-- OPENSPEC_IDEA_ENRICHMENT_END -->@@ -12,6 +12,7 @@ provides_tools:
1212 - openspec_idea_enrich1313 - openspec_idea_promote1414 - openspec_task_list15+ - openspec_change_sequence_set1516 - openspec_task_set_status1617 - openspec_change_create1718 - openspec_change_promote@@ -21,3 +22,4 @@ provides_tools:
2122 - openspec_spec_create2223 - openspec_spec_show2324 - openspec_spec_list25+ - openspec_cli@@ -9,14 +9,16 @@ maps the change token to the matching change folder.
99 The DB lives at ``<hermes_home>/openspec.db`` and is shared between the dashboard1010 plugin (which owns source CRUD via its ``plugin_api.py`` routes) and the agent1111 plugin (which only reads to resolve sources). Schema is intentionally tiny —12-one row per source. Individual changes are NOT stored; they are addressed live13-by deterministic tokens derived from their folder names, so the registry never14-drifts from the files on disk.12+one row per source. Change contents are not stored; changes are addressed live13+by deterministic tokens derived from their folder names. The only per-change14+metadata kept here is dashboard-local sequence order, assigned when the15+dashboard first observes a change so OpenSpec files remain untouched.1516 """1617 1718 from __future__ import annotations1819 1920 import hashlib21+import json2022 import secrets2123 import sqlite32224 import time@@ -55,9 +57,51 @@ def _connect() -> sqlite3.Connection:
5557 conn.execute(5658 "CREATE UNIQUE INDEX IF NOT EXISTS idx_openspec_sources_path ON openspec_sources(path)"5759 )60+ conn.execute(61+ """62+ CREATE TABLE IF NOT EXISTS change_sequence (63+ source_id TEXT NOT NULL,64+ change_name TEXT NOT NULL,65+ group_id TEXT NOT NULL DEFAULT 'default',66+ position INTEGER NOT NULL,67+ depends_on TEXT NOT NULL DEFAULT '[]',68+ first_seen_at REAL NOT NULL,69+ updated_at REAL NOT NULL,70+ PRIMARY KEY (source_id, change_name)71+ )72+ """73+ )74+ _ensure_change_sequence_columns(conn)75+ conn.execute(76+ "CREATE INDEX IF NOT EXISTS idx_change_sequence_source_position ON change_sequence(source_id, position)"77+ )5878 return conn5979 6080 81+def _ensure_change_sequence_columns(conn: sqlite3.Connection) -> None:82+ cols = {row[1] for row in conn.execute("PRAGMA table_info(change_sequence)").fetchall()}83+ if "group_id" not in cols:84+ conn.execute("ALTER TABLE change_sequence ADD COLUMN group_id TEXT NOT NULL DEFAULT 'default'")85+ if "depends_on" not in cols:86+ conn.execute("ALTER TABLE change_sequence ADD COLUMN depends_on TEXT NOT NULL DEFAULT '[]'")87+88+89+def _sequence_row(row: sqlite3.Row) -> dict[str, Any]:90+ try:91+ depends_on = json.loads(row["depends_on"] or "[]")92+ except Exception:93+ depends_on = []94+ if not isinstance(depends_on, list):95+ depends_on = []96+ return {97+ "groupId": row["group_id"] or "default",98+ "position": int(row["position"]),99+ "dependsOn": [str(item) for item in depends_on],100+ "firstSeenAt": row["first_seen_at"],101+ "updatedAt": row["updated_at"],102+ }103+104+61105 def _gen_token(conn: sqlite3.Connection) -> str:62106 for _ in range(64):63107 token = _TOKEN_PREFIX + secrets.token_hex(_TOKEN_NBYTES)@@ -195,10 +239,122 @@ def remove_source(token: str) -> bool:
195239 return False196240 with _connect() as conn:197241 cur = conn.execute("DELETE FROM openspec_sources WHERE token = ?", (token,))242+ conn.execute("DELETE FROM change_sequence WHERE source_id = ?", (token,))198243 conn.commit()199244 return cur.rowcount > 0200245 201246 247+def ensure_change_sequence(source_id: str, change_names: list[str]) -> dict[str, dict[str, Any]]:248+ """Ensure dashboard-local sequence rows exist for observed changes.249+250+ Existing positions are preserved. Newly observed change folders append after251+ the current max position in the order provided by the scanner. This keeps252+ sequencing out of OpenSpec files and avoids parsing naming conventions.253+ """254+ source_id = (source_id or "").strip()255+ names: list[str] = []256+ seen: set[str] = set()257+ for raw in change_names or []:258+ name = str(raw or "").strip()259+ if name and name not in seen:260+ names.append(name)261+ seen.add(name)262+ if not source_id or not names:263+ return {}264+265+ now = time.time()266+ with _connect() as conn:267+ rows = conn.execute(268+ "SELECT change_name, group_id, position, depends_on, first_seen_at, updated_at FROM change_sequence WHERE source_id = ?",269+ (source_id,),270+ ).fetchall()271+ existing = {r["change_name"]: r for r in rows}272+ max_pos = max((int(r["position"]) for r in rows), default=0)273+ for name in names:274+ if name in existing:275+ continue276+ max_pos += 1277+ conn.execute(278+ "INSERT INTO change_sequence (source_id, change_name, position, first_seen_at, updated_at) VALUES (?, ?, ?, ?, ?)",279+ (source_id, name, max_pos, now, now),280+ )281+ conn.commit()282+ rows = conn.execute(283+ "SELECT change_name, group_id, position, depends_on, first_seen_at, updated_at FROM change_sequence WHERE source_id = ?",284+ (source_id,),285+ ).fetchall()286+287+ wanted = set(names)288+ return {289+ r["change_name"]: _sequence_row(r)290+ for r in rows291+ if r["change_name"] in wanted292+ }293+294+295+def get_change_sequence(source_id: str, change_names: list[str] | None = None) -> dict[str, dict[str, Any]]:296+ """Return dashboard-local sequence metadata for a source."""297+ source_id = (source_id or "").strip()298+ if not source_id:299+ return {}300+ names = {str(name or "").strip() for name in (change_names or []) if str(name or "").strip()}301+ with _connect() as conn:302+ rows = conn.execute(303+ "SELECT change_name, group_id, position, depends_on, first_seen_at, updated_at FROM change_sequence WHERE source_id = ? ORDER BY position ASC, change_name ASC",304+ (source_id,),305+ ).fetchall()306+ return {307+ r["change_name"]: _sequence_row(r)308+ for r in rows309+ if not names or r["change_name"] in names310+ }311+312+313+def set_change_sequence(314+ source_id: str,315+ change_names: list[str],316+ *,317+ group_id: str = "default",318+ dependencies: dict[str, list[str]] | None = None,319+) -> dict[str, dict[str, Any]]:320+ """Explicitly set agent-declared order/dependencies for a set of changes."""321+ source_id = (source_id or "").strip()322+ group_id = (group_id or "default").strip() or "default"323+ names: list[str] = []324+ seen: set[str] = set()325+ for raw in change_names or []:326+ name = str(raw or "").strip()327+ if name and name not in seen:328+ names.append(name)329+ seen.add(name)330+ if not source_id or not names:331+ return {}332+ deps = dependencies or {}333+ now = time.time()334+ with _connect() as conn:335+ existing_rows = conn.execute(336+ "SELECT change_name, first_seen_at FROM change_sequence WHERE source_id = ?",337+ (source_id,),338+ ).fetchall()339+ first_seen = {r["change_name"]: r["first_seen_at"] for r in existing_rows}340+ for index, name in enumerate(names, start=1):341+ depends_on = [str(item).strip() for item in deps.get(name, []) if str(item).strip()]342+ conn.execute(343+ """344+ INSERT INTO change_sequence (source_id, change_name, group_id, position, depends_on, first_seen_at, updated_at)345+ VALUES (?, ?, ?, ?, ?, ?, ?)346+ ON CONFLICT(source_id, change_name) DO UPDATE SET347+ group_id = excluded.group_id,348+ position = excluded.position,349+ depends_on = excluded.depends_on,350+ updated_at = excluded.updated_at351+ """,352+ (source_id, name, group_id, index, json.dumps(depends_on), first_seen.get(name, now), now),353+ )354+ conn.commit()355+ return get_change_sequence(source_id, names)356+357+202358 def parse_identifier(identifier: str) -> tuple[str, Optional[str]]:203359 """Split a pasted identifier into ``(source, change_token | None)``.204360 @@ -239,6 +239,38 @@ OPENSPEC_TASK_LIST = {
239239 "parameters": {"type": "object", "properties": {**PROJECT_PROPS, "change": {"type": "string"}, "change_id": {"type": "string"}}, "required": ["change"]},240240 }241241 242+OPENSPEC_CHANGE_SEQUENCE_SET = {243+ "name": "openspec_change_sequence_set",244+ "description": (245+ "Declare dashboard-local review order and dependency/proof relationships for active OpenSpec changes. "246+ "Use after creating multiple related changes so the dashboard and later agents know the intended sequence. "247+ "Stores metadata in the Hermes OpenSpec plugin DB only; it does not modify OpenSpec files."248+ ),249+ "parameters": {250+ "type": "object",251+ "properties": {252+ **PROJECT_PROPS,253+ "changes": {254+ "type": "array",255+ "items": {"type": "string"},256+ "description": "Ordered change names or tokens, e.g. five prerequisite changes followed by the final proving change.",257+ },258+ "group_id": {"type": "string", "description": "Optional sequence group label. Defaults to 'default'."},259+ "dependencies": {260+ "type": "object",261+ "additionalProperties": {"type": "array", "items": {"type": "string"}},262+ "description": "Optional map of change name/token -> prerequisite/proving change names/tokens. Example: {final: [phase1, phase2]}.",263+ },264+ "depends_on": {265+ "type": "object",266+ "additionalProperties": {"type": "array", "items": {"type": "string"}},267+ "description": "Alias for dependencies.",268+ },269+ },270+ "required": ["changes"],271+ },272+}273+242274 OPENSPEC_TASK_SET_STATUS = {243275 "name": "openspec_task_set_status",244276 "description": "Set selected checklist task ids in an OpenSpec change to todo or done.",@@ -419,6 +451,41 @@ OPENSPEC_SPEC_SHOW = {
419451 },420452 }421453 454+OPENSPEC_CLI = {455+ "name": "openspec_cli",456+ "description": (457+ "Run the openspec CLI binary directly and return raw output. "458+ "Use this for CLI commands that need the exact upstream JSON format "459+ "(e.g. `openspec status --change <name> --json` returns applyRequires, "460+ "artifacts, contextFiles, actionContext). The plugin's other tools "461+ "(openspec_show, openspec_status, etc.) return different JSON shapes — "462+ "use openspec_cli when you need the CLI's native output. "463+ "Requires the openspec binary to be installed."464+ ),465+ "parameters": {466+ "type": "object",467+ "properties": {468+ "command": {469+ "type": "string",470+ "description": (471+ "Openspec CLI command and arguments without the binary name "472+ "(e.g. 'status --change my-change', 'list --specs', "473+ "'instructions apply --change my-change')."474+ ),475+ },476+ "workdir": {477+ "type": "string",478+ "description": "Working directory (repo root). Defaults to current directory.",479+ },480+ "json_output": {481+ "type": "boolean",482+ "description": "If true (default), appends --json to the command and returns parsed JSON. If false, returns raw text output.",483+ },484+ },485+ "required": ["command"],486+ },487+}488+422489 OPENSPEC_SPEC_LIST = {423490 "name": "openspec_spec_list",424491 "description": (@@ -0,0 +1,159 @@
1+---2+name: openspec-apply-change3+description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Implement tasks from an OpenSpec change.13+14+**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.15+16+**Steps**17+18+1. **Select the change**19+20+ If a name is provided, use it. Otherwise:21+ - Infer from conversation context if the user mentioned a change22+ - Auto-select if only one active change exists23+ - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select24+25+ Always announce: "Using change: <name>" and how to override (e.g., `/opsx:apply <other>`).26+27+2. **Check status to understand the schema**28+ ```bash29+ openspec status --change "<name>" --json30+ ```31+ Parse the JSON to understand:32+ - `schemaName`: The workflow being used (e.g., "spec-driven")33+ - `planningHome`, `changeRoot`, and `actionContext`: planning scope and edit constraints34+ - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)35+36+3. **Get apply instructions**37+38+ ```bash39+ openspec instructions apply --change "<name>" --json40+ ```41+42+ This returns:43+ - `contextFiles`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)44+ - Progress (total, complete, remaining)45+ - Task list with status46+ - Dynamic instruction based on current state47+48+ **Handle states:**49+ - If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change50+ - If `state: "all_done"`: congratulate, suggest archive51+ - Otherwise: proceed to implementation52+53+ **Workspace guard:** If status JSON reports `actionContext.mode: "workspace-planning"` and `allowedEditRoots` is empty, explain that full workspace apply is not supported in this slice. Treat linked repos and folders as read-only context, ask the user to select an affected area through an explicit implementation workflow, and STOP before editing files.54+55+4. **Read context files**56+57+ Read every file path listed under `contextFiles` from the apply instructions output.58+ The files depend on the schema being used:59+ - **spec-driven**: proposal, specs, design, tasks60+ - Other schemas: follow the contextFiles from CLI output61+62+5. **Show current progress**63+64+ Display:65+ - Schema being used66+ - Progress: "N/M tasks complete"67+ - Remaining tasks overview68+ - Dynamic instruction from CLI69+70+6. **Implement tasks (loop until done or blocked)**71+72+ For each pending task:73+ - Show which task is being worked on74+ - Make the code changes required75+ - Keep changes minimal and focused76+ - Mark task complete in the tasks file: `- [ ]` → `- [x]`77+ - Continue to next task78+79+ **Pause if:**80+ - Task is unclear → ask for clarification81+ - Implementation reveals a design issue → suggest updating artifacts82+ - Error or blocker encountered → report and wait for guidance83+ - User interrupts84+85+7. **On completion or pause, show status**86+87+ Display:88+ - Tasks completed this session89+ - Overall progress: "N/M tasks complete"90+ - If all done: suggest archive91+ - If paused: explain why and wait for guidance92+93+**Output During Implementation**94+95+```96+## Implementing: <change-name> (schema: <schema-name>)97+98+Working on task 3/7: <task description>99+[...implementation happening...]100+✓ Task complete101+102+Working on task 4/7: <task description>103+[...implementation happening...]104+✓ Task complete105+```106+107+**Output On Completion**108+109+```110+## Implementation Complete111+112+**Change:** <change-name>113+**Schema:** <schema-name>114+**Progress:** 7/7 tasks complete ✓115+116+### Completed This Session117+- [x] Task 1118+- [x] Task 2119+...120+121+All tasks complete! Ready to archive this change.122+```123+124+**Output On Pause (Issue Encountered)**125+126+```127+## Implementation Paused128+129+**Change:** <change-name>130+**Schema:** <schema-name>131+**Progress:** 4/7 tasks complete132+133+### Issue Encountered134+<description of the issue>135+136+**Options:**137+1. <option 1>138+2. <option 2>139+3. Other approach140+141+What would you like to do?142+```143+144+**Guardrails**145+- Keep going through tasks until done or blocked146+- Always read context files before starting (from the apply instructions output)147+- If task is ambiguous, pause and ask before implementing148+- If implementation reveals issues, pause and suggest artifact updates149+- Keep code changes minimal and scoped to each task150+- Update task checkbox immediately after completing each task151+- Pause on errors, blockers, or unclear requirements - don't guess152+- Use contextFiles from CLI output, don't assume specific file names153+154+**Fluid Workflow Integration**155+156+This skill supports the "actions on a change" model:157+158+- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions159+- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly@@ -0,0 +1,117 @@
1+---2+name: openspec-archive-change3+description: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Archive a completed change in the experimental workflow.13+14+**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.15+16+**Steps**17+18+1. **If no change name provided, prompt for selection**19+20+ Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.21+22+ Show only active changes (not already archived).23+ Include the schema used for each change if available.24+25+ **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.26+27+2. **Check artifact completion status**28+29+ Run `openspec status --change "<name>" --json` to check artifact completion.30+31+ Parse the JSON to understand:32+ - `schemaName`: The workflow being used33+ - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context34+ - `artifacts`: List of artifacts with their status (`done` or other)35+36+ If status reports `actionContext.mode: "workspace-planning"`, explain that workspace archive is not supported in this slice and STOP. Do not move workspace changes into repo-local archives or edit linked repos.37+38+ **If any artifacts are not `done`:**39+ - Display warning listing incomplete artifacts40+ - Use **AskUserQuestion tool** to confirm user wants to proceed41+ - Proceed if user confirms42+43+3. **Check task completion status**44+45+ Read the tasks file (typically `tasks.md`) to check for incomplete tasks.46+47+ Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).48+49+ **If incomplete tasks found:**50+ - Display warning showing count of incomplete tasks51+ - Use **AskUserQuestion tool** to confirm user wants to proceed52+ - Proceed if user confirms53+54+ **If no tasks file exists:** Proceed without task-related warning.55+56+4. **Assess delta spec sync state**57+58+ Use `artifactPaths.specs.existingOutputPaths` from status JSON to check for delta specs. If none exist, proceed without sync prompt.59+60+ **If delta specs exist:**61+ - Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`62+ - Determine what changes would be applied (adds, modifications, removals, renames)63+ - Show a combined summary before prompting64+65+ **Prompt options:**66+ - If changes needed: "Sync now (recommended)", "Archive without syncing"67+ - If already synced: "Archive now", "Sync anyway", "Cancel"68+69+ If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.70+71+5. **Perform the archive**72+73+ Create an `archive` directory under `planningHome.changesDir` if it doesn't exist:74+ ```bash75+ mkdir -p "<planningHome.changesDir>/archive"76+ ```77+78+ Generate target name using current date: `YYYY-MM-DD-<change-name>`79+80+ **Check if target already exists:**81+ - If yes: Fail with error, suggest renaming existing archive or using different date82+ - If no: Move `changeRoot` to the archive directory83+84+ ```bash85+ mv "<changeRoot>" "<planningHome.changesDir>/archive/YYYY-MM-DD-<name>"86+ ```87+88+6. **Display summary**89+90+ Show archive completion summary including:91+ - Change name92+ - Schema that was used93+ - Archive location94+ - Whether specs were synced (if applicable)95+ - Note about any warnings (incomplete artifacts/tasks)96+97+**Output On Success**98+99+```100+## Archive Complete101+102+**Change:** <change-name>103+**Schema:** <schema-name>104+**Archived to:** the archive path derived from `planningHome.changesDir`/YYYY-MM-DD-<name>/105+**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped")106+107+All artifacts complete. All tasks complete.108+```109+110+**Guardrails**111+- Always prompt for change selection if not provided112+- Use artifact graph (openspec status --json) for completion checking113+- Don't block archive on warnings - just inform and confirm114+- Preserve .openspec.yaml when moving to archive (it moves with the directory)115+- Show clear summary of what happened116+- If sync is requested, use openspec-sync-specs approach (agent-driven)117+- If delta specs exist, always run the sync assessment and show the combined summary before prompting@@ -0,0 +1,248 @@
1+---2+name: openspec-bulk-archive-change3+description: Archive multiple completed changes at once. Use when archiving several parallel changes.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Archive multiple completed changes in a single operation.13+14+This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented.15+16+**Input**: None required (prompts for selection)17+18+**Steps**19+20+1. **Get active changes**21+22+ Run `openspec list --json` to get all active changes.23+24+ If no active changes exist, inform user and stop.25+26+2. **Prompt for change selection**27+28+ Use **AskUserQuestion tool** with multi-select to let user choose changes:29+ - Show each change with its schema30+ - Include an option for "All changes"31+ - Allow any number of selections (1+ works, 2+ is the typical use case)32+33+ **IMPORTANT**: Do NOT auto-select. Always let the user choose.34+35+3. **Batch validation - gather status for all selected changes**36+37+ For each selected change, collect:38+39+ a. **Artifact status** - Run `openspec status --change "<name>" --json`40+ - Parse `schemaName`, `artifacts`, `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`41+ - Note which artifacts are `done` vs other states42+43+ If any selected change reports `actionContext.mode: "workspace-planning"`, explain that workspace bulk archive is not supported in this slice and STOP before syncing specs or moving changes. Do not fall back to repo-local paths or edit linked repos.44+45+ b. **Task completion** - Read `artifactPaths.tasks.existingOutputPaths` from status JSON46+ - Count `- [ ]` (incomplete) vs `- [x]` (complete)47+ - If no tasks file exists, note as "No tasks"48+49+ c. **Delta specs** - Check `artifactPaths.specs.existingOutputPaths` from status JSON50+ - List which capability specs exist51+ - For each, extract requirement names (lines matching `### Requirement: <name>`)52+53+4. **Detect spec conflicts**54+55+ Build a map of `capability -> [changes that touch it]`:56+57+ ```58+ auth -> [change-a, change-b] <- CONFLICT (2+ changes)59+ api -> [change-c] <- OK (only 1 change)60+ ```61+62+ A conflict exists when 2+ selected changes have delta specs for the same capability.63+64+5. **Resolve conflicts agentically**65+66+ **For each conflict**, investigate the codebase:67+68+ a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify69+70+ b. **Search the codebase** for implementation evidence:71+ - Look for code implementing requirements from each delta spec72+ - Check for related files, functions, or tests73+74+ c. **Determine resolution**:75+ - If only one change is actually implemented -> sync that one's specs76+ - If both implemented -> apply in chronological order (older first, newer overwrites)77+ - If neither implemented -> skip spec sync, warn user78+79+ d. **Record resolution** for each conflict:80+ - Which change's specs to apply81+ - In what order (if both)82+ - Rationale (what was found in codebase)83+84+6. **Show consolidated status table**85+86+ Display a table summarizing all changes:87+88+ ```89+ | Change | Artifacts | Tasks | Specs | Conflicts | Status |90+ |---------------------|-----------|-------|---------|-----------|--------|91+ | schema-management | Done | 5/5 | 2 delta | None | Ready |92+ | project-config | Done | 3/3 | 1 delta | None | Ready |93+ | add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* |94+ | add-verify-skill | 1 left | 2/5 | None | None | Warn |95+ ```96+97+ For conflicts, show the resolution:98+ ```99+ * Conflict resolution:100+ - auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order)101+ ```102+103+ For incomplete changes, show warnings:104+ ```105+ Warnings:106+ - add-verify-skill: 1 incomplete artifact, 3 incomplete tasks107+ ```108+109+7. **Confirm batch operation**110+111+ Use **AskUserQuestion tool** with a single confirmation:112+113+ - "Archive N changes?" with options based on status114+ - Options might include:115+ - "Archive all N changes"116+ - "Archive only N ready changes (skip incomplete)"117+ - "Cancel"118+119+ If there are incomplete changes, make clear they'll be archived with warnings.120+121+8. **Execute archive for each confirmed change**122+123+ Process changes in the determined order (respecting conflict resolution):124+125+ a. **Sync specs** if delta specs exist:126+ - Use the openspec-sync-specs approach (agent-driven intelligent merge)127+ - For conflicts, apply in resolved order128+ - Track if sync was done129+130+ b. **Perform the archive**:131+ ```bash132+ mkdir -p "<planningHome.changesDir>/archive"133+ mv "<changeRoot>" "<planningHome.changesDir>/archive/YYYY-MM-DD-<name>"134+ ```135+136+ c. **Track outcome** for each change:137+ - Success: archived successfully138+ - Failed: error during archive (record error)139+ - Skipped: user chose not to archive (if applicable)140+141+9. **Display summary**142+143+ Show final results:144+145+ ```146+ ## Bulk Archive Complete147+148+ Archived 3 changes:149+ - schema-management-cli -> archive/2026-01-19-schema-management-cli/150+ - project-config -> archive/2026-01-19-project-config/151+ - add-oauth -> archive/2026-01-19-add-oauth/152+153+ Skipped 1 change:154+ - add-verify-skill (user chose not to archive incomplete)155+156+ Spec sync summary:157+ - 4 delta specs synced to main specs158+ - 1 conflict resolved (auth: applied both in chronological order)159+ ```160+161+ If any failures:162+ ```163+ Failed 1 change:164+ - some-change: Archive directory already exists165+ ```166+167+**Conflict Resolution Examples**168+169+Example 1: Only one implemented170+```171+Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt]172+173+Checking add-oauth:174+- Delta adds "OAuth Provider Integration" requirement175+- Searching codebase... found src/auth/oauth.ts implementing OAuth flow176+177+Checking add-jwt:178+- Delta adds "JWT Token Handling" requirement179+- Searching codebase... no JWT implementation found180+181+Resolution: Only add-oauth is implemented. Will sync add-oauth specs only.182+```183+184+Example 2: Both implemented185+```186+Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql]187+188+Checking add-rest-api (created 2026-01-10):189+- Delta adds "REST Endpoints" requirement190+- Searching codebase... found src/api/rest.ts191+192+Checking add-graphql (created 2026-01-15):193+- Delta adds "GraphQL Schema" requirement194+- Searching codebase... found src/api/graphql.ts195+196+Resolution: Both implemented. Will apply add-rest-api specs first,197+then add-graphql specs (chronological order, newer takes precedence).198+```199+200+**Output On Success**201+202+```203+## Bulk Archive Complete204+205+Archived N changes:206+- <change-1> -> archive/YYYY-MM-DD-<change-1>/207+- <change-2> -> archive/YYYY-MM-DD-<change-2>/208+209+Spec sync summary:210+- N delta specs synced to main specs211+- No conflicts (or: M conflicts resolved)212+```213+214+**Output On Partial Success**215+216+```217+## Bulk Archive Complete (partial)218+219+Archived N changes:220+- <change-1> -> archive/YYYY-MM-DD-<change-1>/221+222+Skipped M changes:223+- <change-2> (user chose not to archive incomplete)224+225+Failed K changes:226+- <change-3>: Archive directory already exists227+```228+229+**Output When No Changes**230+231+```232+## No Changes to Archive233+234+No active changes found. Create a new change to get started.235+```236+237+**Guardrails**238+- Allow any number of changes (1+ is fine, 2+ is the typical use case)239+- Always prompt for selection, never auto-select240+- Detect spec conflicts early and resolve by checking codebase241+- When both changes are implemented, apply specs in chronological order242+- Skip spec sync only when implementation is missing (warn user)243+- Show clear per-change status before confirming244+- Use single confirmation for entire batch245+- Track and report all outcomes (success/skip/fail)246+- Preserve .openspec.yaml when moving to archive247+- Archive directory target uses current date: YYYY-MM-DD-<name>248+- If archive target exists, fail that change but continue with others@@ -0,0 +1,119 @@
1+---2+name: openspec-continue-change3+description: Continue working on an OpenSpec change by creating the next artifact. Use when the user wants to progress their change, create the next artifact, or continue their workflow.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Continue working on a change by creating the next artifact.13+14+**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.15+16+**Steps**17+18+1. **If no change name provided, prompt for selection**19+20+ Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to work on.21+22+ Present the top 3-4 most recently modified changes as options, showing:23+ - Change name24+ - Schema (from `schema` field if present, otherwise "spec-driven")25+ - Status (e.g., "0/5 tasks", "complete", "no tasks")26+ - How recently it was modified (from `lastModified` field)27+28+ Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to continue.29+30+ **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.31+32+2. **Check current status**33+ ```bash34+ openspec status --change "<name>" --json35+ ```36+ Parse the JSON to understand current state. The response includes:37+ - `schemaName`: The workflow schema being used (e.g., "spec-driven")38+ - `artifacts`: Array of artifacts with their status ("done", "ready", "blocked")39+ - `isComplete`: Boolean indicating if all artifacts are complete40+ - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.41+42+3. **Act based on status**:43+44+ ---45+46+ **If all artifacts are complete (`isComplete: true`)**:47+ - Congratulate the user48+ - Show final status including the schema used49+ - Suggest: "All artifacts created! You can now implement this change or archive it."50+ - STOP51+52+ ---53+54+ **If artifacts are ready to create** (status shows artifacts with `status: "ready"`):55+ - Pick the FIRST artifact with `status: "ready"` from the status output56+ - Get its instructions:57+ ```bash58+ openspec instructions <artifact-id> --change "<name>" --json59+ ```60+ - Parse the JSON. The key fields are:61+ - `context`: Project background (constraints for you - do NOT include in output)62+ - `rules`: Artifact-specific rules (constraints for you - do NOT include in output)63+ - `template`: The structure to use for your output file64+ - `instruction`: Schema-specific guidance65+ - `resolvedOutputPath`: Resolved path or pattern to write the artifact66+ - `dependencies`: Completed artifacts to read for context67+ - **Create the artifact file**:68+ - Read any completed dependency files for context69+ - Use `template` as the structure - fill in its sections70+ - Apply `context` and `rules` as constraints when writing - but do NOT copy them into the file71+ - Write to the `resolvedOutputPath` specified in instructions. If it is a glob pattern, choose the concrete file path using the schema instruction and workspace planning context72+ - Show what was created and what's now unlocked73+ - STOP after creating ONE artifact74+75+ ---76+77+ **If no artifacts are ready (all blocked)**:78+ - This shouldn't happen with a valid schema79+ - Show status and suggest checking for issues80+81+4. **After creating an artifact, show progress**82+ ```bash83+ openspec status --change "<name>"84+ ```85+86+**Output**87+88+After each invocation, show:89+- Which artifact was created90+- Schema workflow being used91+- Current progress (N/M complete)92+- What artifacts are now unlocked93+- Prompt: "Want to continue? Just ask me to continue or tell me what to do next."94+95+**Artifact Creation Guidelines**96+97+The artifact types and their purpose depend on the schema. Use the `instruction` field from the instructions output to understand what to create.98+99+Common artifact patterns:100+101+**spec-driven schema** (proposal → specs → design → tasks):102+- **proposal.md**: Ask user about the change if not clear. Fill in Why, What Changes, Capabilities, Impact.103+ - The Capabilities section is critical - each capability listed will need a spec file.104+- **specs/<capability>/spec.md**: Create one spec per capability listed in the proposal's Capabilities section (use the capability name, not the change name).105+- **design.md**: Document technical decisions, architecture, and implementation approach.106+- **tasks.md**: Break down implementation into checkboxed tasks.107+108+For other schemas, follow the `instruction` field from the CLI output.109+110+**Guardrails**111+- Create ONE artifact per invocation112+- Always read dependency artifacts before creating a new one113+- Never skip artifacts or create out of order114+- If context is unclear, ask the user before creating115+- Verify the artifact file exists after writing before marking progress116+- Use the schema's artifact sequence, don't assume specific artifact names117+- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file118+ - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact119+ - These guide what you write, but should never appear in the output@@ -0,0 +1,287 @@
1+---2+name: openspec-explore3+description: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.13+14+**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.15+16+**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.17+18+---19+20+## The Stance21+22+- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script23+- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.24+- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking25+- **Adaptive** - Follow interesting threads, pivot when new information emerges26+- **Patient** - Don't rush to conclusions, let the shape of the problem emerge27+- **Grounded** - Explore the actual codebase when relevant, don't just theorize28+29+---30+31+## What You Might Do32+33+Depending on what the user brings, you might:34+35+**Explore the problem space**36+- Ask clarifying questions that emerge from what they said37+- Challenge assumptions38+- Reframe the problem39+- Find analogies40+41+**Investigate the codebase**42+- Map existing architecture relevant to the discussion43+- Find integration points44+- Identify patterns already in use45+- Surface hidden complexity46+47+**Compare options**48+- Brainstorm multiple approaches49+- Build comparison tables50+- Sketch tradeoffs51+- Recommend a path (if asked)52+53+**Visualize**54+```55+┌─────────────────────────────────────────┐56+│ Use ASCII diagrams liberally │57+├─────────────────────────────────────────┤58+│ │59+│ ┌────────┐ ┌────────┐ │60+│ │ State │────────▶│ State │ │61+│ │ A │ │ B │ │62+│ └────────┘ └────────┘ │63+│ │64+│ System diagrams, state machines, │65+│ data flows, architecture sketches, │66+│ dependency graphs, comparison tables │67+│ │68+└─────────────────────────────────────────┘69+```70+71+**Surface risks and unknowns**72+- Identify what could go wrong73+- Find gaps in understanding74+- Suggest spikes or investigations75+76+---77+78+## OpenSpec Awareness79+80+You have full context of the OpenSpec system. Use it naturally, don't force it.81+82+### Check for context83+84+At the start, quickly check what exists:85+```bash86+openspec list --json87+```88+89+This tells you:90+- If there are active changes91+- Their names, schemas, and status92+- What the user might be working on93+94+### When no change exists95+96+Think freely. When insights crystallize, you might offer:97+98+- "This feels solid enough to start a change. Want me to create a proposal?"99+- Or keep exploring - no pressure to formalize100+101+### When a change exists102+103+If the user mentions a change or you detect one is relevant:104+105+1. **Resolve and read existing artifacts for context**106+ - Run `openspec status --change "<name>" --json`.107+ - Use `changeRoot`, `artifactPaths`, and `actionContext` from the status JSON.108+ - Read existing files from `artifactPaths.<artifact>.existingOutputPaths`.109+110+2. **Reference them naturally in conversation**111+ - "Your design mentions using Redis, but we just realized SQLite fits better..."112+ - "The proposal scopes this to premium users, but we're now thinking everyone..."113+114+3. **Offer to capture when decisions are made**115+116+ | Insight Type | Where to Capture |117+ |----------------------------|--------------------------------|118+ | New requirement discovered | `specs/<capability>/spec.md` |119+ | Requirement changed | `specs/<capability>/spec.md` |120+ | Design decision made | `design.md` |121+ | Scope changed | `proposal.md` |122+ | New work identified | `tasks.md` |123+ | Assumption invalidated | Relevant artifact |124+125+ Example offers:126+ - "That's a design decision. Capture it in design.md?"127+ - "This is a new requirement. Add it to specs?"128+ - "This changes scope. Update the proposal?"129+130+4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.131+132+---133+134+## What You Don't Have To Do135+136+- Follow a script137+- Ask the same questions every time138+- Produce a specific artifact139+- Reach a conclusion140+- Stay on topic if a tangent is valuable141+- Be brief (this is thinking time)142+143+---144+145+## Handling Different Entry Points146+147+**User brings a vague idea:**148+```149+User: I'm thinking about adding real-time collaboration150+151+You: Real-time collab is a big space. Let me think about this...152+153+ COLLABORATION SPECTRUM154+ ════════════════════════════════════════════155+156+ Awareness Coordination Sync157+ │ │ │158+ ▼ ▼ ▼159+ ┌────────┐ ┌────────┐ ┌────────┐160+ │Presence│ │Cursors │ │ CRDT │161+ │ "3 │ │ Multi │ │Conflict│162+ │online" │ │ select │ │ free │163+ └────────┘ └────────┘ └────────┘164+ │ │ │165+ trivial moderate complex166+167+ Where's your head at?168+```169+170+**User brings a specific problem:**171+```172+User: The auth system is a mess173+174+You: [reads codebase]175+176+ ┌─────────────────────────────────────────────┐177+ │ CURRENT AUTH FLOW │178+ └─────────────────────────────────────────────┘179+ │180+ ┌─────────────┼─────────────┐181+ ▼ ▼ ▼182+ ┌─────────┐ ┌─────────┐ ┌─────────┐183+ │ Google │ │ GitHub │ │ Email │184+ │ OAuth │ │ OAuth │ │ Magic │185+ └────┬────┘ └────┬────┘ └────┬────┘186+ │ │ │187+ └─────────────┼─────────────┘188+ ▼189+ ┌───────────┐190+ │ Session │191+ └─────┬─────┘192+ │193+ ▼194+ ┌───────────┐195+ │ Perms │196+ └───────────┘197+198+ I see three tangles. Which one's burning?199+```200+201+**User is stuck mid-implementation:**202+```203+User: /opsx:explore add-auth-system204+ The OAuth integration is more complex than expected205+206+You: [reads change artifacts]207+208+ You're on task 4: "Implement OAuth flow"209+210+ Let me trace what's involved...211+212+ [draws diagram, explores options, suggests paths]213+214+ Want to update the design to reflect this?215+ Or add a spike task to investigate?216+```217+218+**User wants to compare options:**219+```220+User: Should we use Postgres or SQLite?221+222+You: Generic answer is boring. What's the context?223+224+User: A CLI tool that tracks local dev environments225+226+You: That changes everything.227+228+ ┌─────────────────────────────────────────────────┐229+ │ CLI TOOL DATA STORAGE │230+ └─────────────────────────────────────────────────┘231+232+ Key constraints:233+ • No daemon running234+ • Must work offline235+ • Single user236+237+ SQLite Postgres238+ Deployment embedded ✓ needs server ✗239+ Offline yes ✓ no ✗240+ Single file yes ✓ no ✗241+242+ SQLite. Not even close.243+244+ Unless... is there a sync component?245+```246+247+---248+249+## Ending Discovery250+251+There's no required ending. Discovery might:252+253+- **Flow into a proposal**: "Ready to start? I can create a change proposal."254+- **Result in artifact updates**: "Updated design.md with these decisions"255+- **Just provide clarity**: User has what they need, moves on256+- **Continue later**: "We can pick this up anytime"257+258+When it feels like things are crystallizing, you might summarize:259+260+```261+## What We Figured Out262+263+**The problem**: [crystallized understanding]264+265+**The approach**: [if one emerged]266+267+**Open questions**: [if any remain]268+269+**Next steps** (if ready):270+- Create a change proposal271+- Keep exploring: just keep talking272+```273+274+But this summary is optional. Sometimes the thinking IS the value.275+276+---277+278+## Guardrails279+280+- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.281+- **Don't fake understanding** - If something is unclear, dig deeper282+- **Don't rush** - Discovery is thinking time, not task time283+- **Don't force structure** - Let patterns emerge naturally284+- **Don't auto-capture** - Offer to save insights, don't just do it285+- **Do visualize** - A good diagram is worth many paragraphs286+- **Do explore the codebase** - Ground discussions in reality287+- **Do question assumptions** - Including the user's and your own@@ -0,0 +1,102 @@
1+---2+name: openspec-ff-change3+description: Fast-forward through OpenSpec artifact creation. Use when the user wants to quickly create all artifacts needed for implementation without stepping through each one individually.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Fast-forward through artifact creation - generate everything needed to start implementation in one go.13+14+**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.15+16+**Steps**17+18+1. **If no clear input provided, ask what they want to build**19+20+ Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:21+ > "What change do you want to work on? Describe what you want to build or fix."22+23+ From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).24+25+ **IMPORTANT**: Do NOT proceed without understanding what the user wants to build.26+27+2. **Create the change directory**28+ ```bash29+ openspec new change "<name>"30+ ```31+ This creates a scaffolded change in the planning home resolved by the CLI.32+33+3. **Get the artifact build order**34+ ```bash35+ openspec status --change "<name>" --json36+ ```37+ Parse the JSON to get:38+ - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)39+ - `artifacts`: list of all artifacts with their status and dependencies40+ - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.41+42+4. **Create artifacts in sequence until apply-ready**43+44+ Use the **TodoWrite tool** to track progress through the artifacts.45+46+ Loop through artifacts in dependency order (artifacts with no pending dependencies first):47+48+ a. **For each artifact that is `ready` (dependencies satisfied)**:49+ - Get instructions:50+ ```bash51+ openspec instructions <artifact-id> --change "<name>" --json52+ ```53+ - The instructions JSON includes:54+ - `context`: Project background (constraints for you - do NOT include in output)55+ - `rules`: Artifact-specific rules (constraints for you - do NOT include in output)56+ - `template`: The structure to use for your output file57+ - `instruction`: Schema-specific guidance for this artifact type58+ - `resolvedOutputPath`: Resolved path or pattern to write the artifact59+ - `dependencies`: Completed artifacts to read for context60+ - Read any completed dependency files for context61+ - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`62+ - Apply `context` and `rules` as constraints - but do NOT copy them into the file63+ - Show brief progress: "✓ Created <artifact-id>"64+65+ b. **Continue until all `applyRequires` artifacts are complete**66+ - After creating each artifact, re-run `openspec status --change "<name>" --json`67+ - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array68+ - Stop when all `applyRequires` artifacts are done69+70+ c. **If an artifact requires user input** (unclear context):71+ - Use **AskUserQuestion tool** to clarify72+ - Then continue with creation73+74+5. **Show final status**75+ ```bash76+ openspec status --change "<name>"77+ ```78+79+**Output**80+81+After completing all artifacts, summarize:82+- Change name and location83+- List of artifacts created with brief descriptions84+- What's ready: "All artifacts created! Ready for implementation."85+- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks."86+87+**Artifact Creation Guidelines**88+89+- Follow the `instruction` field from `openspec instructions` for each artifact type90+- The schema defines what each artifact should contain - follow it91+- Read dependency artifacts for context before creating new ones92+- Use `template` as the structure for your output file - fill in its sections93+- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file94+ - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact95+ - These guide what you write, but should never appear in the output96+97+**Guardrails**98+- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)99+- Always read dependency artifacts before creating a new one100+- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum101+- If a change with that name already exists, suggest continuing that change instead102+- Verify each artifact file exists after writing before proceeding to next@@ -0,0 +1,74 @@
1+---2+name: openspec-new-change3+description: Start a new OpenSpec change using the experimental artifact workflow. Use when the user wants to create a new feature, fix, or modification with a structured step-by-step approach.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Start a new change using the experimental artifact-driven approach.13+14+**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.15+16+**Steps**17+18+1. **If no clear input provided, ask what they want to build**19+20+ Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:21+ > "What change do you want to work on? Describe what you want to build or fix."22+23+ From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).24+25+ **IMPORTANT**: Do NOT proceed without understanding what the user wants to build.26+27+2. **Determine the workflow schema**28+29+ Use the default schema (omit `--schema`) unless the user explicitly requests a different workflow.30+31+ **Use a different schema only if the user mentions:**32+ - A specific schema name → use `--schema <name>`33+ - "show workflows" or "what workflows" → run `openspec schemas --json` and let them choose34+35+ **Otherwise**: Omit `--schema` to use the default.36+37+3. **Create the change directory**38+ ```bash39+ openspec new change "<name>"40+ ```41+ Add `--schema <name>` only if the user requested a specific workflow.42+ This creates a scaffolded change in the planning home resolved by the CLI.43+44+4. **Show the artifact status**45+ ```bash46+ openspec status --change "<name>" --json47+ ```48+ Use the returned `planningHome`, `changeRoot`, `artifactPaths`, and `nextSteps` instead of assuming repo-local paths.49+50+5. **Get instructions for the first artifact**51+ The first artifact depends on the schema (e.g., `proposal` for spec-driven).52+ Check the status output to find the first artifact with status "ready".53+ ```bash54+ openspec instructions <first-artifact-id> --change "<name>"55+ ```56+ This outputs the template and context for creating the first artifact.57+58+6. **STOP and wait for user direction**59+60+**Output**61+62+After completing the steps, summarize:63+- Change name and location64+- Schema/workflow being used and its artifact sequence65+- Current status (0/N artifacts complete)66+- The template for the first artifact67+- Prompt: "Ready to create the first artifact? Just describe what this change is about and I'll draft it, or ask me to continue."68+69+**Guardrails**70+- Do NOT create any artifacts yet - just show the instructions71+- Do NOT advance beyond showing the first artifact template72+- If the name is invalid (not kebab-case), ask for a valid name73+- If a change with that name already exists, suggest continuing that change instead74+- Pass --schema if using a non-default workflow@@ -0,0 +1,552 @@
1+---2+name: openspec-onboard3+description: Guided onboarding for OpenSpec - walk through a complete workflow cycle with narration and real codebase work.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step.13+14+---15+16+## Preflight17+18+Before starting, check if the OpenSpec CLI is installed:19+20+```bash21+# Unix/macOS22+openspec --version 2>&1 || echo "CLI_NOT_INSTALLED"23+# Windows (PowerShell)24+# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" }25+```26+27+**If CLI not installed:**28+> OpenSpec CLI is not installed. Install it first, then come back to `/opsx:onboard`.29+30+Stop here if not installed.31+32+---33+34+## Phase 1: Welcome35+36+Display:37+38+```39+## Welcome to OpenSpec!40+41+I'll walk you through a complete change cycle—from idea to implementation—using a real task in your codebase. Along the way, you'll learn the workflow by doing it.42+43+**What we'll do:**44+1. Pick a small, real task in your codebase45+2. Explore the problem briefly46+3. Create a change (the container for our work)47+4. Build the artifacts: proposal → specs → design → tasks48+5. Implement the tasks49+6. Archive the completed change50+51+**Time:** ~15-20 minutes52+53+Let's start by finding something to work on.54+```55+56+---57+58+## Phase 2: Task Selection59+60+### Codebase Analysis61+62+Scan the codebase for small improvement opportunities. Look for:63+64+1. **TODO/FIXME comments** - Search for `TODO`, `FIXME`, `HACK`, `XXX` in code files65+2. **Missing error handling** - `catch` blocks that swallow errors, risky operations without try-catch66+3. **Functions without tests** - Cross-reference `src/` with test directories67+4. **Type issues** - `any` types in TypeScript files (`: any`, `as any`)68+5. **Debug artifacts** - `console.log`, `console.debug`, `debugger` statements in non-debug code69+6. **Missing validation** - User input handlers without validation70+71+Also check recent git activity:72+```bash73+# Unix/macOS74+git log --oneline -10 2>/dev/null || echo "No git history"75+# Windows (PowerShell)76+# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" }77+```78+79+### Present Suggestions80+81+From your analysis, present 3-4 specific suggestions:82+83+```84+## Task Suggestions85+86+Based on scanning your codebase, here are some good starter tasks:87+88+**1. [Most promising task]**89+ Location: `src/path/to/file.ts:42`90+ Scope: ~1-2 files, ~20-30 lines91+ Why it's good: [brief reason]92+93+**2. [Second task]**94+ Location: `src/another/file.ts`95+ Scope: ~1 file, ~15 lines96+ Why it's good: [brief reason]97+98+**3. [Third task]**99+ Location: [location]100+ Scope: [estimate]101+ Why it's good: [brief reason]102+103+**4. Something else?**104+ Tell me what you'd like to work on.105+106+Which task interests you? (Pick a number or describe your own)107+```108+109+**If nothing found:** Fall back to asking what the user wants to build:110+> I didn't find obvious quick wins in your codebase. What's something small you've been meaning to add or fix?111+112+### Scope Guardrail113+114+If the user picks or describes something too large (major feature, multi-day work):115+116+```117+That's a valuable task, but it's probably larger than ideal for your first OpenSpec run-through.118+119+For learning the workflow, smaller is better—it lets you see the full cycle without getting stuck in implementation details.120+121+**Options:**122+1. **Slice it smaller** - What's the smallest useful piece of [their task]? Maybe just [specific slice]?123+2. **Pick something else** - One of the other suggestions, or a different small task?124+3. **Do it anyway** - If you really want to tackle this, we can. Just know it'll take longer.125+126+What would you prefer?127+```128+129+Let the user override if they insist—this is a soft guardrail.130+131+---132+133+## Phase 3: Explore Demo134+135+Once a task is selected, briefly demonstrate explore mode:136+137+```138+Before we create a change, let me quickly show you **explore mode**—it's how you think through problems before committing to a direction.139+```140+141+Spend 1-2 minutes investigating the relevant code:142+- Read the file(s) involved143+- Draw a quick ASCII diagram if it helps144+- Note any considerations145+146+```147+## Quick Exploration148+149+[Your brief analysis—what you found, any considerations]150+151+┌─────────────────────────────────────────┐152+│ [Optional: ASCII diagram if helpful] │153+└─────────────────────────────────────────┘154+155+Explore mode (`/opsx:explore`) is for this kind of thinking—investigating before implementing. You can use it anytime you need to think through a problem.156+157+Now let's create a change to hold our work.158+```159+160+**PAUSE** - Wait for user acknowledgment before proceeding.161+162+---163+164+## Phase 4: Create the Change165+166+**EXPLAIN:**167+```168+## Creating a Change169+170+A "change" in OpenSpec is a container for all the thinking and planning around a piece of work. It lives at the `changeRoot` reported by `openspec status --change "<name>" --json` and holds your artifacts—proposal, specs, design, tasks.171+172+Let me create one for our task.173+```174+175+**DO:** Create the change with a derived kebab-case name:176+```bash177+openspec new change "<derived-name>"178+```179+180+**SHOW:**181+```182+Created: <changeRoot from status JSON>183+184+The folder structure:185+```186+<changeRoot>/187+├── proposal.md ← Why we're doing this (empty, we'll fill it)188+├── design.md ← How we'll build it (empty)189+├── specs/ ← Detailed requirements (empty)190+└── tasks.md ← Implementation checklist (empty)191+```192+193+Now let's fill in the first artifact—the proposal.194+```195+196+---197+198+## Phase 5: Proposal199+200+**EXPLAIN:**201+```202+## The Proposal203+204+The proposal captures **why** we're making this change and **what** it involves at a high level. It's the "elevator pitch" for the work.205+206+I'll draft one based on our task.207+```208+209+**DO:** Draft the proposal content (don't save yet):210+211+```212+Here's a draft proposal:213+214+---215+216+## Why217+218+[1-2 sentences explaining the problem/opportunity]219+220+## What Changes221+222+[Bullet points of what will be different]223+224+## Capabilities225+226+### New Capabilities227+- `<capability-name>`: [brief description]228+229+### Modified Capabilities230+<!-- If modifying existing behavior -->231+232+## Impact233+234+- `src/path/to/file.ts`: [what changes]235+- [other files if applicable]236+237+---238+239+Does this capture the intent? I can adjust before we save it.240+```241+242+**PAUSE** - Wait for user approval/feedback.243+244+After approval, save the proposal:245+```bash246+openspec instructions proposal --change "<name>" --json247+```248+Then write the content to the `resolvedOutputPath` from `openspec instructions proposal --change "<name>" --json`.249+250+```251+Proposal saved. This is your "why" document—you can always come back and refine it as understanding evolves.252+253+Next up: specs.254+```255+256+---257+258+## Phase 6: Specs259+260+**EXPLAIN:**261+```262+## Specs263+264+Specs define **what** we're building in precise, testable terms. They use a requirement/scenario format that makes expected behavior crystal clear.265+266+For a small task like this, we might only need one spec file.267+```268+269+**DO:** Resolve where the spec file should be created:270+```bash271+openspec instructions specs --change "<name>" --json272+# Use resolvedOutputPath from the JSON. If it is a glob, choose the concrete file path using the schema instruction and workspace planning context.273+```274+275+Draft the spec content:276+277+```278+Here's the spec:279+280+---281+282+## ADDED Requirements283+284+### Requirement: <Name>285+286+<Description of what the system should do>287+288+#### Scenario: <Scenario name>289+290+- **WHEN** <trigger condition>291+- **THEN** <expected outcome>292+- **AND** <additional outcome if needed>293+294+---295+296+This format—WHEN/THEN/AND—makes requirements testable. You can literally read them as test cases.297+```298+299+Save to the concrete file path chosen from `resolvedOutputPath`.300+301+---302+303+## Phase 7: Design304+305+**EXPLAIN:**306+```307+## Design308+309+The design captures **how** we'll build it—technical decisions, tradeoffs, approach.310+311+For small changes, this might be brief. That's fine—not every change needs deep design discussion.312+```313+314+**DO:** Draft design.md:315+316+```317+Here's the design:318+319+---320+321+## Context322+323+[Brief context about the current state]324+325+## Goals / Non-Goals326+327+**Goals:**328+- [What we're trying to achieve]329+330+**Non-Goals:**331+- [What's explicitly out of scope]332+333+## Decisions334+335+### Decision 1: [Key decision]336+337+[Explanation of approach and rationale]338+339+---340+341+For a small task, this captures the key decisions without over-engineering.342+```343+344+Save to the `resolvedOutputPath` from `openspec instructions design --change "<name>" --json`.345+346+---347+348+## Phase 8: Tasks349+350+**EXPLAIN:**351+```352+## Tasks353+354+Finally, we break the work into implementation tasks—checkboxes that drive the apply phase.355+356+These should be small, clear, and in logical order.357+```358+359+**DO:** Generate tasks based on specs and design:360+361+```362+Here are the implementation tasks:363+364+---365+366+## 1. [Category or file]367+368+- [ ] 1.1 [Specific task]369+- [ ] 1.2 [Specific task]370+371+## 2. Verify372+373+- [ ] 2.1 [Verification step]374+375+---376+377+Each checkbox becomes a unit of work in the apply phase. Ready to implement?378+```379+380+**PAUSE** - Wait for user to confirm they're ready to implement.381+382+Save to the `resolvedOutputPath` from `openspec instructions tasks --change "<name>" --json`.383+384+---385+386+## Phase 9: Apply (Implementation)387+388+**EXPLAIN:**389+```390+## Implementation391+392+Now we implement each task, checking them off as we go. I'll announce each one and occasionally note how the specs/design informed the approach.393+```394+395+**DO:** For each task:396+397+1. Announce: "Working on task N: [description]"398+2. Implement the change in the codebase399+3. Reference specs/design naturally: "The spec says X, so I'm doing Y"400+4. Mark complete in tasks.md: `- [ ]` → `- [x]`401+5. Brief status: "✓ Task N complete"402+403+Keep narration light—don't over-explain every line of code.404+405+After all tasks:406+407+```408+## Implementation Complete409+410+All tasks done:411+- [x] Task 1412+- [x] Task 2413+- [x] ...414+415+The change is implemented! One more step—let's archive it.416+```417+418+---419+420+## Phase 10: Archive421+422+**EXPLAIN:**423+```424+## Archiving425+426+When a change is complete, we archive it. The archive path is derived from `planningHome.changesDir` and the date.427+428+Archived changes become your project's decision history—you can always find them later to understand why something was built a certain way.429+```430+431+**DO:**432+```bash433+openspec archive "<name>"434+```435+436+**SHOW:**437+```438+Archived to: `<planningHome.changesDir>/archive/YYYY-MM-DD-<name>/`439+440+The change is now part of your project's history. The code is in your codebase, the decision record is preserved.441+```442+443+---444+445+## Phase 11: Recap & Next Steps446+447+```448+## Congratulations!449+450+You just completed a full OpenSpec cycle:451+452+1. **Explore** - Thought through the problem453+2. **New** - Created a change container454+3. **Proposal** - Captured WHY455+4. **Specs** - Defined WHAT in detail456+5. **Design** - Decided HOW457+6. **Tasks** - Broke it into steps458+7. **Apply** - Implemented the work459+8. **Archive** - Preserved the record460+461+This same rhythm works for any size change—a small fix or a major feature.462+463+---464+465+## Command Reference466+467+**Core workflow:**468+469+ | Command | What it does |470+ |-------------------|--------------------------------------------|471+ | `/opsx:propose` | Create a change and generate all artifacts |472+ | `/opsx:explore` | Think through problems before/during work |473+ | `/opsx:apply` | Implement tasks from a change |474+ | `/opsx:archive` | Archive a completed change |475+476+**Additional commands:**477+478+ | Command | What it does |479+ |--------------------|----------------------------------------------------------|480+ | `/opsx:new` | Start a new change, step through artifacts one at a time |481+ | `/opsx:continue` | Continue working on an existing change |482+ | `/opsx:ff` | Fast-forward: create all artifacts at once |483+ | `/opsx:verify` | Verify implementation matches artifacts |484+485+---486+487+## What's Next?488+489+Try `/opsx:propose` on something you actually want to build. You've got the rhythm now!490+```491+492+---493+494+## Graceful Exit Handling495+496+### User wants to stop mid-way497+498+If the user says they need to stop, want to pause, or seem disengaged:499+500+```501+No problem! Your change is saved at the `changeRoot` reported by `openspec status --change "<name>" --json`.502+503+To pick up where we left off later:504+- `/opsx:continue <name>` - Resume artifact creation505+- `/opsx:apply <name>` - Jump to implementation (if tasks exist)506+507+The work won't be lost. Come back whenever you're ready.508+```509+510+Exit gracefully without pressure.511+512+### User just wants command reference513+514+If the user says they just want to see the commands or skip the tutorial:515+516+```517+## OpenSpec Quick Reference518+519+**Core workflow:**520+521+ | Command | What it does |522+ |--------------------------|--------------------------------------------|523+ | `/opsx:propose <name>` | Create a change and generate all artifacts |524+ | `/opsx:explore` | Think through problems (no code changes) |525+ | `/opsx:apply <name>` | Implement tasks |526+ | `/opsx:archive <name>` | Archive when done |527+528+**Additional commands:**529+530+ | Command | What it does |531+ |---------------------------|-------------------------------------|532+ | `/opsx:new <name>` | Start a new change, step by step |533+ | `/opsx:continue <name>` | Continue an existing change |534+ | `/opsx:ff <name>` | Fast-forward: all artifacts at once |535+ | `/opsx:verify <name>` | Verify implementation |536+537+Try `/opsx:propose` to start your first change.538+```539+540+Exit gracefully.541+542+---543+544+## Guardrails545+546+- **Follow the EXPLAIN → DO → SHOW → PAUSE pattern** at key transitions (after explore, after proposal draft, after tasks, after archive)547+- **Keep narration light** during implementation—teach without lecturing548+- **Don't skip phases** even if the change is small—the goal is teaching the workflow549+- **Pause for acknowledgment** at marked points, but don't over-pause550+- **Handle exits gracefully**—never pressure the user to continue551+- **Use real codebase tasks**—don't simulate or use fake examples552+- **Adjust scope gently**—guide toward smaller tasks but respect user choice@@ -0,0 +1,111 @@
1+---2+name: openspec-propose3+description: Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Propose a new change - create the change and generate all artifacts in one step.13+14+I'll create a change with artifacts:15+- proposal.md (what & why)16+- design.md (how)17+- tasks.md (implementation steps)18+19+When ready to implement, run /opsx:apply20+21+---22+23+**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.24+25+**Steps**26+27+1. **If no clear input provided, ask what they want to build**28+29+ Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:30+ > "What change do you want to work on? Describe what you want to build or fix."31+32+ From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).33+34+ **IMPORTANT**: Do NOT proceed without understanding what the user wants to build.35+36+2. **Create the change directory**37+ ```bash38+ openspec new change "<name>"39+ ```40+ This creates a scaffolded change in the planning home resolved by the CLI with `.openspec.yaml`.41+42+3. **Get the artifact build order**43+ ```bash44+ openspec status --change "<name>" --json45+ ```46+ Parse the JSON to get:47+ - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)48+ - `artifacts`: list of all artifacts with their status and dependencies49+ - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context. Use these instead of assuming repo-local paths.50+51+4. **Create artifacts in sequence until apply-ready**52+53+ Use the **TodoWrite tool** to track progress through the artifacts.54+55+ Loop through artifacts in dependency order (artifacts with no pending dependencies first):56+57+ a. **For each artifact that is `ready` (dependencies satisfied)**:58+ - Get instructions:59+ ```bash60+ openspec instructions <artifact-id> --change "<name>" --json61+ ```62+ - The instructions JSON includes:63+ - `context`: Project background (constraints for you - do NOT include in output)64+ - `rules`: Artifact-specific rules (constraints for you - do NOT include in output)65+ - `template`: The structure to use for your output file66+ - `instruction`: Schema-specific guidance for this artifact type67+ - `resolvedOutputPath`: Resolved path or pattern to write the artifact68+ - `dependencies`: Completed artifacts to read for context69+ - Read any completed dependency files for context70+ - Create the artifact file using `template` as the structure and write it to `resolvedOutputPath`71+ - Apply `context` and `rules` as constraints - but do NOT copy them into the file72+ - Show brief progress: "Created <artifact-id>"73+74+ b. **Continue until all `applyRequires` artifacts are complete**75+ - After creating each artifact, re-run `openspec status --change "<name>" --json`76+ - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array77+ - Stop when all `applyRequires` artifacts are done78+79+ c. **If an artifact requires user input** (unclear context):80+ - Use **AskUserQuestion tool** to clarify81+ - Then continue with creation82+83+5. **Show final status**84+ ```bash85+ openspec status --change "<name>"86+ ```87+88+**Output**89+90+After completing all artifacts, summarize:91+- Change name and location92+- List of artifacts created with brief descriptions93+- What's ready: "All artifacts created! Ready for implementation."94+- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks."95+96+**Artifact Creation Guidelines**97+98+- Follow the `instruction` field from `openspec instructions` for each artifact type99+- The schema defines what each artifact should contain - follow it100+- Read dependency artifacts for context before creating new ones101+- Use `template` as the structure for your output file - fill in its sections102+- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file103+ - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact104+ - These guide what you write, but should never appear in the output105+106+**Guardrails**107+- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)108+- Always read dependency artifacts before creating a new one109+- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum110+- If a change with that name already exists, ask if user wants to continue it or create a new one111+- Verify each artifact file exists after writing before proceeding to next@@ -0,0 +1,147 @@
1+---2+name: openspec-sync-specs3+description: Sync delta specs from a change to main specs. Use when the user wants to update main specs with changes from a delta spec, without archiving the change.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Sync delta specs from a change to main specs.13+14+This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement).15+16+**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.17+18+**Steps**19+20+1. **If no change name provided, prompt for selection**21+22+ Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.23+24+ Show changes that have delta specs (under `specs/` directory).25+26+ **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.27+28+2. **Resolve change context**29+30+ Run:31+ ```bash32+ openspec status --change "<name>" --json33+ ```34+35+ If status reports `actionContext.mode: "workspace-planning"`, explain that workspace spec sync is not supported in this slice and STOP. Do not fall back to repo-local paths or edit linked repos.36+37+3. **Find delta specs**38+39+ Use `artifactPaths.specs.existingOutputPaths` from the status JSON as the list of delta spec files.40+41+ Each delta spec file contains sections like:42+ - `## ADDED Requirements` - New requirements to add43+ - `## MODIFIED Requirements` - Changes to existing requirements44+ - `## REMOVED Requirements` - Requirements to remove45+ - `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format)46+47+ If no delta specs found, inform user and stop.48+49+4. **For each delta spec, apply changes to main specs**50+51+ For each repo-local capability delta spec path returned by the CLI:52+53+ a. **Read the delta spec** to understand the intended changes54+55+ b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet)56+57+ c. **Apply changes intelligently**:58+59+ **ADDED Requirements:**60+ - If requirement doesn't exist in main spec → add it61+ - If requirement already exists → update it to match (treat as implicit MODIFIED)62+63+ **MODIFIED Requirements:**64+ - Find the requirement in main spec65+ - Apply the changes - this can be:66+ - Adding new scenarios (don't need to copy existing ones)67+ - Modifying existing scenarios68+ - Changing the requirement description69+ - Preserve scenarios/content not mentioned in the delta70+71+ **REMOVED Requirements:**72+ - Remove the entire requirement block from main spec73+74+ **RENAMED Requirements:**75+ - Find the FROM requirement, rename to TO76+77+ d. **Create new main spec** if capability doesn't exist yet:78+ - Create `openspec/specs/<capability>/spec.md`79+ - Add Purpose section (can be brief, mark as TBD)80+ - Add Requirements section with the ADDED requirements81+82+5. **Show summary**83+84+ After applying all changes, summarize:85+ - Which capabilities were updated86+ - What changes were made (requirements added/modified/removed/renamed)87+88+**Delta Spec Format Reference**89+90+```markdown91+## ADDED Requirements92+93+### Requirement: New Feature94+The system SHALL do something new.95+96+#### Scenario: Basic case97+- **WHEN** user does X98+- **THEN** system does Y99+100+## MODIFIED Requirements101+102+### Requirement: Existing Feature103+#### Scenario: New scenario to add104+- **WHEN** user does A105+- **THEN** system does B106+107+## REMOVED Requirements108+109+### Requirement: Deprecated Feature110+111+## RENAMED Requirements112+113+- FROM: `### Requirement: Old Name`114+- TO: `### Requirement: New Name`115+```116+117+**Key Principle: Intelligent Merging**118+119+Unlike programmatic merging, you can apply **partial updates**:120+- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios121+- The delta represents *intent*, not a wholesale replacement122+- Use your judgment to merge changes sensibly123+124+**Output On Success**125+126+```127+## Specs Synced: <change-name>128+129+Updated main specs:130+131+**<capability-1>**:132+- Added requirement: "New Feature"133+- Modified requirement: "Existing Feature" (added 1 scenario)134+135+**<capability-2>**:136+- Created new spec file137+- Added requirement: "Another Feature"138+139+Main specs are now updated. The change remains active - archive when implementation is complete.140+```141+142+**Guardrails**143+- Read both delta and main specs before making changes144+- Preserve existing content not mentioned in delta145+- If something is unclear, ask for clarification146+- Show what you're changing as you go147+- The operation should be idempotent - running twice should give same result@@ -0,0 +1,171 @@
1+---2+name: openspec-verify-change3+description: Verify implementation matches change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving.4+license: MIT5+compatibility: Requires openspec CLI.6+metadata:7+ author: openspec8+ version: "1.0"9+ generatedBy: "1.4.1"10+---11+12+Verify that an implementation matches the change artifacts (specs, tasks, design).13+14+**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.15+16+**Steps**17+18+1. **If no change name provided, prompt for selection**19+20+ Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.21+22+ Show changes that have implementation tasks (tasks artifact exists).23+ Include the schema used for each change if available.24+ Mark changes with incomplete tasks as "(In Progress)".25+26+ **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.27+28+2. **Check status to understand the schema**29+ ```bash30+ openspec status --change "<name>" --json31+ ```32+ Parse the JSON to understand:33+ - `schemaName`: The workflow being used (e.g., "spec-driven")34+ - `planningHome`, `changeRoot`, `artifactPaths`, and `actionContext`: path and scope context35+ - Which artifacts exist for this change36+37+ If status reports `actionContext.mode: "workspace-planning"`, explain that full workspace implementation verification is not supported in this slice and STOP. Do not infer repo-local implementation ownership or edit linked repos.38+39+3. **Get planning context and load artifacts**40+41+ ```bash42+ openspec instructions apply --change "<name>" --json43+ ```44+45+ This returns the change directory and `contextFiles` (artifact ID -> array of concrete file paths). Read all available artifacts from `contextFiles`.46+47+4. **Initialize verification report structure**48+49+ Create a report structure with three dimensions:50+ - **Completeness**: Track tasks and spec coverage51+ - **Correctness**: Track requirement implementation and scenario coverage52+ - **Coherence**: Track design adherence and pattern consistency53+54+ Each dimension can have CRITICAL, WARNING, or SUGGESTION issues.55+56+5. **Verify Completeness**57+58+ **Task Completion**:59+ - If `contextFiles.tasks` exists, read every file path in it60+ - Parse checkboxes: `- [ ]` (incomplete) vs `- [x]` (complete)61+ - Count complete vs total tasks62+ - If incomplete tasks exist:63+ - Add CRITICAL issue for each incomplete task64+ - Recommendation: "Complete task: <description>" or "Mark as done if already implemented"65+66+ **Spec Coverage**:67+ - If delta specs exist in `contextFiles.specs`:68+ - Extract all requirements (marked with "### Requirement:")69+ - For each requirement:70+ - Search codebase for keywords related to the requirement71+ - Assess if implementation likely exists72+ - If requirements appear unimplemented:73+ - Add CRITICAL issue: "Requirement not found: <requirement name>"74+ - Recommendation: "Implement requirement X: <description>"75+76+6. **Verify Correctness**77+78+ **Requirement Implementation Mapping**:79+ - For each requirement from delta specs:80+ - Search codebase for implementation evidence81+ - If found, note file paths and line ranges82+ - Assess if implementation matches requirement intent83+ - If divergence detected:84+ - Add WARNING: "Implementation may diverge from spec: <details>"85+ - Recommendation: "Review <file>:<lines> against requirement X"86+87+ **Scenario Coverage**:88+ - For each scenario in delta specs (marked with "#### Scenario:"):89+ - Check if conditions are handled in code90+ - Check if tests exist covering the scenario91+ - If scenario appears uncovered:92+ - Add WARNING: "Scenario not covered: <scenario name>"93+ - Recommendation: "Add test or implementation for scenario: <description>"94+95+7. **Verify Coherence**96+97+ **Design Adherence**:98+ - If `contextFiles.design` exists:99+ - Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:")100+ - Verify implementation follows those decisions101+ - If contradiction detected:102+ - Add WARNING: "Design decision not followed: <decision>"103+ - Recommendation: "Update implementation or revise design.md to match reality"104+ - If no design.md: Skip design adherence check, note "No design.md to verify against"105+106+ **Code Pattern Consistency**:107+ - Review new code for consistency with project patterns108+ - Check file naming, directory structure, coding style109+ - If significant deviations found:110+ - Add SUGGESTION: "Code pattern deviation: <details>"111+ - Recommendation: "Consider following project pattern: <example>"112+113+8. **Generate Verification Report**114+115+ **Summary Scorecard**:116+ ```117+ ## Verification Report: <change-name>118+119+ ### Summary120+ | Dimension | Status |121+ |--------------|------------------|122+ | Completeness | X/Y tasks, N reqs|123+ | Correctness | M/N reqs covered |124+ | Coherence | Followed/Issues |125+ ```126+127+ **Issues by Priority**:128+129+ 1. **CRITICAL** (Must fix before archive):130+ - Incomplete tasks131+ - Missing requirement implementations132+ - Each with specific, actionable recommendation133+134+ 2. **WARNING** (Should fix):135+ - Spec/design divergences136+ - Missing scenario coverage137+ - Each with specific recommendation138+139+ 3. **SUGGESTION** (Nice to fix):140+ - Pattern inconsistencies141+ - Minor improvements142+ - Each with specific recommendation143+144+ **Final Assessment**:145+ - If CRITICAL issues: "X critical issue(s) found. Fix before archiving."146+ - If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)."147+ - If all clear: "All checks passed. Ready for archive."148+149+**Verification Heuristics**150+151+- **Completeness**: Focus on objective checklist items (checkboxes, requirements list)152+- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty153+- **Coherence**: Look for glaring inconsistencies, don't nitpick style154+- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL155+- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable156+157+**Graceful Degradation**158+159+- If only tasks.md exists: verify task completion only, skip spec/design checks160+- If tasks + specs exist: verify completeness and correctness, skip design161+- If full artifacts: verify all three dimensions162+- Always note which checks were skipped and why163+164+**Output Format**165+166+Use clear markdown with:167+- Table for summary scorecard168+- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION)169+- Code references in format: `file.ts:123`170+- Specific, actionable recommendations171+- No vague suggestions like "consider reviewing"@@ -7,6 +7,7 @@ from pathlib import Path
77 import pytest88 99 import tools10+import registry1011 from dashboard import plugin_api1112 1213 @@ -58,3 +59,71 @@ def test_dashboard_init_fallback_layout_matches_supported_scan_roots(tmp_path, m
5859 assert (openspec_root / "specs").is_dir()5960 assert (openspec_root / "ideas").is_dir()6061 assert response["source"]["valid"] is True62+63+64+def test_registry_change_sequence_appends_and_preserves_existing_positions(tmp_path, monkeypatch):65+ monkeypatch.setattr(registry, "db_path", lambda: tmp_path / "openspec.db")66+67+ first = registry.ensure_change_sequence("os_test", ["second", "first"])68+ assert first["second"]["position"] == 169+ assert first["first"]["position"] == 270+71+ second = registry.ensure_change_sequence("os_test", ["first", "third", "second"])72+ assert second["second"]["position"] == 173+ assert second["first"]["position"] == 274+ assert second["third"]["position"] == 375+76+77+def test_dashboard_scan_attaches_sequence_without_openspec_files(tmp_path, monkeypatch):78+ openspec_root = tmp_path / "openspec" / "changes"79+ for name in ["alpha", "beta"]:80+ change_dir = openspec_root / name81+ change_dir.mkdir(parents=True)82+ (change_dir / "proposal.md").write_text(f"# {name.title()}\n", encoding="utf-8")83+84+ class FakeRegistry:85+ def change_token(self, name):86+ return "os_" + name87+88+ def ensure_change_sequence(self, source_id, names):89+ assert source_id == "os_test"90+ return {name: {"position": i + 1, "firstSeenAt": 1.0, "updatedAt": 1.0} for i, name in enumerate(names)}91+92+ monkeypatch.setattr(plugin_api, "_registry", FakeRegistry())93+ payload = plugin_api._scan(tmp_path, "os_test")94+95+ assert payload is not None96+ assert [ch["name"] for ch in payload["changes"]] == ["alpha", "beta"]97+ assert [ch["sequence"]["position"] for ch in payload["changes"]] == [1, 2]98+ assert not (openspec_root / "alpha" / ".sequence").exists()99+100+101+def test_change_sequence_tool_declares_order_and_dependencies(tmp_path, monkeypatch):102+ monkeypatch.setattr(registry, "db_path", lambda: tmp_path / "openspec.db")103+ monkeypatch.setattr(tools, "_registry_module", lambda: registry)104+ project = tmp_path / "project"105+ changes_root = project / "openspec" / "changes"106+ names = ["phase-one", "phase-two", "final-proof"]107+ for name in names:108+ change_dir = changes_root / name109+ change_dir.mkdir(parents=True)110+ (change_dir / "proposal.md").write_text(f"# {name}\n", encoding="utf-8")111+ registry.add_source(str(project), "demo")112+113+ result = json.loads(tools.openspec_change_sequence_set({114+ "identifier": "demo",115+ "changes": names,116+ "group_id": "poc",117+ "dependencies": {"final-proof": ["phase-one", "phase-two"]},118+ }))119+120+ assert result["ok"] is True121+ assert [item["name"] for item in result["changes"]] == names122+ assert result["changes"][0]["sequence"]["position"] == 1123+ assert result["changes"][2]["sequence"]["groupId"] == "poc"124+ assert result["changes"][2]["sequence"]["dependsOn"] == ["phase-one", "phase-two"]125+ assert not (changes_root / "final-proof" / ".sequence").exists()126+127+ context = json.loads(tools.openspec_context({"identifier": "demo"}))128+ final = next(item for item in context["changes"] if item["name"] == "final-proof")129+ assert final["sequence"]["dependsOn"] == ["phase-one", "phase-two"]@@ -300,10 +300,17 @@ def openspec_context(args: dict, **kwargs) -> str:
300300 "specs": specs,301301 }302302 else:303+ change_dirs = [(change_dir, archived) for change_dir, archived in _iter_change_dirs(repo_path) if not archived]304+ sequence = {}305+ if hasattr(registry, "ensure_change_sequence"):306+ sequence = registry.ensure_change_sequence(str(source.get("id") or source.get("token")), [change_dir.name for change_dir, _ in change_dirs])303307 changes = [304- {"name": change_dir.name, "token": registry.change_token(change_dir.name)}305- for change_dir, archived in _iter_change_dirs(repo_path)306- if not archived308+ {309+ "name": change_dir.name,310+ "token": registry.change_token(change_dir.name),311+ **({"sequence": sequence[change_dir.name]} if change_dir.name in sequence else {}),312+ }313+ for change_dir, archived in change_dirs307314 ]308315 specs = [309316 {"path": rel, "token": registry.change_token(f"spec:{rel}")}@@ -422,7 +429,7 @@ def _markdown_list(items: list[str]) -> str:
422429 return "\n".join(f"- {item}" for item in items)423430 424431 425-_TASK_LINE_RE = re.compile(r"^(?P<prefix>\s*- \[)(?P<mark>[ xX])(?P<suffix>\]\s*(?P<id>\d+(?:\.\d+)*)\s+(?P<text>.*?))\s*$")432+_TASK_LINE_RE = re.compile(r"^(?P<prefix>\s*- \[)(?P<mark>[ xX])(?P<suffix>\]\s*(?:(?P<id>\d+(?:\.\d+)*)\s+)?(?P<text>.*?))\s*$")426433 427434 428435 def _change_path(root: Path, change: str, *, archived: bool = False) -> Path:@@ -445,6 +452,24 @@ def _resolve_change_path(root: Path, change_ref: str) -> tuple[Path | None, bool
445452 return None, False446453 447454 455+def _resolve_change_names(root: Path, registry, refs: list[str]) -> tuple[list[str], list[str]]:456+ names: list[str] = []457+ missing: list[str] = []458+ by_name = {change_dir.name: change_dir.name for change_dir, _ in _iter_change_dirs(root)}459+ by_token = {registry.change_token(name): name for name in by_name}460+ for ref in refs:461+ raw = str(ref or "").strip()462+ if not raw:463+ continue464+ name = by_name.get(raw) or by_token.get(raw)465+ if name:466+ if name not in names:467+ names.append(name)468+ else:469+ missing.append(raw)470+ return names, missing471+472+448473 def _derive_status(tasks_path: Path, *, archived: bool = False) -> str:449474 if archived:450475 return "archived"@@ -465,12 +490,15 @@ def _parse_tasks(tasks_path: Path) -> list[dict[str, Any]]:
465490 if not tasks_path.is_file():466491 return []467492 tasks: list[dict[str, Any]] = []493+ seq = 0468494 for line_no, line in enumerate(tasks_path.read_text(encoding="utf-8", errors="replace").splitlines(), start=1):469495 match = _TASK_LINE_RE.match(line)470496 if not match:471497 continue498+ seq += 1499+ task_id = match.group("id") or str(seq)472500 tasks.append({473- "id": match.group("id"),501+ "id": task_id,474502 "text": match.group("text").strip(),475503 "status": "done" if match.group("mark").lower() == "x" else "todo",476504 "line": line_no,@@ -888,6 +916,58 @@ def openspec_change_promote(args: dict, **kwargs) -> str:
888916 })889917 890918 919+def openspec_change_sequence_set(args: dict, **kwargs) -> str:920+ """Set dashboard-local order/dependency metadata for active changes."""921+ root, err, context = _resolve_project(args)922+ if err or root is None:923+ return _error(err or "workdir could not be resolved")924+ registry = _registry_module()925+ if registry is None or not hasattr(registry, "set_change_sequence"):926+ return _error("OpenSpec registry sequence support unavailable")927+ source = context.get("source") if isinstance(context, dict) else None928+ if source is None:929+ source = registry.get_source_by_path(str(root))930+ if source is None:931+ return _error("sequence metadata requires a registered OpenSpec source; pass identifier/project or register the repo in the dashboard", workdir=str(root))932+ raw_changes = _coerce_string_list(args.get("changes"))933+ changes, missing = _resolve_change_names(root, registry, raw_changes)934+ if missing:935+ return _error("some changes were not found", missing=missing, workdir=str(root))936+ if not changes:937+ return _error("changes is required")938+ raw_deps = args.get("dependencies") or args.get("depends_on") or {}939+ if raw_deps is None:940+ raw_deps = {}941+ if not isinstance(raw_deps, dict):942+ return _error("dependencies must be an object mapping change names/tokens to arrays of prerequisite change names/tokens")943+ dependencies: dict[str, list[str]] = {}944+ dep_missing: list[str] = []945+ for raw_key, raw_values in raw_deps.items():946+ key_names, key_missing = _resolve_change_names(root, registry, [str(raw_key)])947+ dep_names, missing_deps = _resolve_change_names(root, registry, _coerce_string_list(raw_values))948+ if key_missing:949+ dep_missing.extend(key_missing)950+ continue951+ if missing_deps:952+ dep_missing.extend(missing_deps)953+ if key_names:954+ dependencies[key_names[0]] = dep_names955+ if dep_missing:956+ return _error("some dependency changes were not found", missing=dep_missing, workdir=str(root))957+ source_id = str(source.get("id") or source.get("token"))958+ group_id = str(args.get("group_id") or args.get("groupId") or "default").strip() or "default"959+ sequence = registry.set_change_sequence(source_id, changes, group_id=group_id, dependencies=dependencies)960+ return json.dumps({961+ "ok": True,962+ "source": registry.effective_name(source),963+ "source_id": source_id,964+ "workdir": str(root),965+ "groupId": group_id,966+ "changes": [{"name": name, "token": registry.change_token(name), "sequence": sequence.get(name, {})} for name in changes],967+ "note": "Sequence/dependency metadata is stored in the Hermes OpenSpec plugin DB, not in OpenSpec files.",968+ })969+970+891971 def openspec_task_list(args: dict, **kwargs) -> str:892972 root, err, _context = _resolve_project(args)893973 if err or root is None:@@ -938,23 +1018,45 @@ def openspec_task_set_status(args: dict, **kwargs) -> str:
9381018 if not tasks_path.is_file():9391019 return _error(f"tasks not found for change: {change}")9401020 lines = tasks_path.read_text(encoding="utf-8", errors="replace").splitlines()941- found: set[str] = set()1021+ # Build a lookup of parsed tasks so we can match by numeric ID or text substring.1022+ parsed = _parse_tasks(tasks_path)1023+ # Resolve each requested task_id to actual line numbers.1024+ # Accept: numeric IDs (exact match), or text substrings (case-insensitive).1025+ target_lines: set[int] = set()1026+ not_found: list[str] = []1027+ for requested in task_ids:1028+ # Try exact numeric ID match first1029+ id_matches = [t for t in parsed if t["id"] == requested]1030+ if id_matches:1031+ for t in id_matches:1032+ target_lines.add(t["line"])1033+ continue1034+ # Try text substring match (case-insensitive)1035+ needle = requested.lower()1036+ text_matches = [t for t in parsed if needle in t["text"].lower()]1037+ if text_matches:1038+ for t in text_matches:1039+ target_lines.add(t["line"])1040+ continue1041+ not_found.append(requested)1042+ if not_found:1043+ return _error("task ids not found: " + ", ".join(not_found), missing=not_found)1044+ # Rewrite matching lines9421045 updated_lines = []943- for line in lines:944- match = _TASK_LINE_RE.match(line)945- if match and match.group("id") in task_ids:946- found.add(match.group("id"))947- line = f"{match.group('prefix')}{mark}{match.group('suffix')}"1046+ matched_ids: set[str] = set()1047+ for line_no, line in enumerate(lines, start=1):1048+ if line_no in target_lines:1049+ match = _TASK_LINE_RE.match(line)1050+ if match:1051+ matched_ids.add(match.group("id") or str(line_no))1052+ line = f"{match.group('prefix')}{mark}{match.group('suffix')}"9481053 updated_lines.append(line)949- missing = sorted(task_ids - found)950- if missing:951- return _error("task ids not found: " + ", ".join(missing), missing=missing)9521054 tasks_path.write_text("\n".join(updated_lines) + "\n", encoding="utf-8")9531055 tasks = _parse_tasks(tasks_path)9541056 return json.dumps({9551057 "ok": True,9561058 "change": change,957- "updated": sorted(found),1059+ "updated": sorted(matched_ids),9581060 "set_status": normalized,9591061 "path": str(tasks_path),9601062 "relative_path": _relative(tasks_path, root),@@ -1393,3 +1495,81 @@ def openspec_spec_list(args: dict, **kwargs) -> str:
13931495 "count": len(names),13941496 "workdir": str(root),13951497 })1498+1499+1500+def openspec_cli(args: dict, **kwargs) -> str:1501+ """Run the openspec CLI binary directly and return raw output.1502+1503+ This is a passthrough tool — it runs ``openspec <command> [--json]`` and1504+ returns the raw stdout. Use this when you need the CLI's native JSON format1505+ (e.g. ``applyRequires``, ``artifacts``, ``contextFiles``) rather than the1506+ plugin's wrapped JSON shapes.1507+1508+ Gated by ``check_fn`` — only appears when the openspec binary is available.1509+ """1510+ import shlex1511+1512+ exe = _openspec_bin()1513+ if exe is None:1514+ return _error("openspec CLI binary not found. Set OPENSPEC_BIN or install via npm.")1515+1516+ raw_command = str(args.get("command") or "").strip()1517+ if not raw_command:1518+ return _error("command is required (e.g. 'status --change my-change')")1519+1520+ try:1521+ cmd_parts = shlex.split(raw_command)1522+ except ValueError:1523+ cmd_parts = raw_command.split()1524+1525+ json_output = args.get("json_output")1526+ if json_output is None:1527+ json_output = True1528+ if json_output:1529+ cmd_parts = [*cmd_parts, "--json"]1530+1531+ workdir = str(args.get("workdir") or "").strip()1532+ if workdir:1533+ root, err = _resolve_workdir(workdir)1534+ if err or root is None:1535+ return _error(err or "workdir could not be resolved")1536+ cwd = str(root)1537+ else:1538+ cwd = None1539+1540+ try:1541+ proc = subprocess.run(1542+ [exe, *cmd_parts],1543+ capture_output=True,1544+ text=True,1545+ timeout=120,1546+ check=False,1547+ cwd=cwd,1548+ )1549+ except subprocess.TimeoutExpired:1550+ return _error("openspec CLI command timed out (120s)")1551+ except OSError as exc:1552+ return _error(f"failed to run openspec CLI: {exc}")1553+1554+ stdout = proc.stdout or ""1555+ stderr = proc.stderr or ""1556+1557+ if json_output and proc.returncode == 0:1558+ try:1559+ parsed = json.loads(stdout)1560+ return json.dumps({1561+ "ok": True,1562+ "exit_code": proc.returncode,1563+ "stdout": parsed,1564+ "workdir": cwd,1565+ })1566+ except json.JSONDecodeError:1567+ pass # Fall through to raw output1568+1569+ return json.dumps({1570+ "ok": proc.returncode == 0,1571+ "exit_code": proc.returncode,1572+ "stdout": stdout,1573+ "stderr": stderr.strip() if stderr else "",1574+ "workdir": cwd,1575+ })