@skitterbyte/skitterspec-azure-boards 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +1126 -0
- package/README.md +55 -2
- package/assets/claude-md-section.md +85 -0
- package/assets/commands/allow-main.md +33 -0
- package/assets/commands/spec-connect.md +13 -0
- package/assets/commands/spec-live.md +14 -0
- package/assets/commands/spec-remote-review.md +37 -0
- package/assets/commands/spec-skip.md +24 -0
- package/assets/core/SETUP.md +214 -0
- package/assets/core/azure-boards.config.json.example +35 -0
- package/assets/core/azure-boards.config.md +43 -0
- package/assets/core/env.config.json.example +56 -0
- package/assets/core/env.config.md +557 -0
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/hooks/main-guard.cjs +162 -0
- package/assets/hooks/review-gate.cjs +146 -0
- package/assets/review/page.html +3384 -0
- package/assets/rules/commit-trailers.md +97 -0
- package/assets/rules/negative-checks.md +69 -0
- package/assets/rules/offered-unblocks.md +100 -0
- package/assets/rules/spec-planning.md +692 -0
- package/assets/rules/spec-reports.md +557 -0
- package/assets/skills/no-spec/SKILL.md +246 -0
- package/assets/skills/spec/SKILL.md +719 -0
- package/assets/skills/spec-azure-setup/SKILL.md +227 -0
- package/assets/skills/spec-bug/SKILL.md +647 -0
- package/assets/skills/spec-cancel/SKILL.md +262 -0
- package/assets/skills/spec-claim/SKILL.md +122 -0
- package/assets/skills/spec-complete/SKILL.md +449 -0
- package/assets/skills/spec-diff/SKILL.md +870 -0
- package/assets/skills/spec-hotfix/SKILL.md +669 -0
- package/assets/skills/spec-init/SKILL.md +178 -0
- package/assets/skills/spec-list/SKILL.md +177 -0
- package/assets/skills/spec-next/SKILL.md +686 -0
- package/assets/skills/spec-push/SKILL.md +304 -0
- package/assets/skills/spec-review/SKILL.md +257 -0
- package/assets/skills/spec-reviewed/SKILL.md +284 -0
- package/assets/skills/spec-start/SKILL.md +554 -0
- package/assets/skills/spec-status/SKILL.md +112 -0
- package/assets/skills/spec-sync/SKILL.md +221 -0
- package/assets/skills/spec-to-main/SKILL.md +139 -0
- package/bin/skitterspec-azure-boards.js +62 -0
- package/package.json +41 -4
- package/src/cli.js +5907 -0
- package/src/deprecate.js +138 -0
- package/src/env/bootstrap.js +188 -0
- package/src/env/building.js +143 -0
- package/src/env/classify.js +176 -0
- package/src/env/commitcmd.js +108 -0
- package/src/env/config.js +753 -0
- package/src/env/dev.js +63 -0
- package/src/env/hooks.js +226 -0
- package/src/env/hotfix.js +411 -0
- package/src/env/hotfixland.js +302 -0
- package/src/env/integrate.js +126 -0
- package/src/env/live.js +504 -0
- package/src/env/mainguard.js +299 -0
- package/src/env/provision.js +466 -0
- package/src/env/proxy.js +225 -0
- package/src/env/prune.js +119 -0
- package/src/env/registry.js +159 -0
- package/src/env/render.js +17 -0
- package/src/env/resolve.js +716 -0
- package/src/env/review.js +2577 -0
- package/src/env/reviewers/coderabbit.js +228 -0
- package/src/env/reviewers.js +517 -0
- package/src/env/serve.js +1044 -0
- package/src/env/supervise.js +157 -0
- package/src/env/teardown.js +248 -0
- package/src/env/trust.js +87 -0
- package/src/gating.js +155 -0
- package/src/init.js +1150 -0
- package/src/lines-diff.js +114 -0
- package/src/prompts.js +129 -0
- package/src/vendor/azure-boards/api.js +561 -0
- package/src/vendor/azure-boards/cli-sync.js +1428 -0
- package/src/vendor/azure-boards/commands.js +44 -0
- package/src/vendor/azure-boards/config.js +365 -0
- package/src/vendor/azure-boards/credentials.js +109 -0
- package/src/vendor/azure-boards/dialect.js +100 -0
- package/src/vendor/azure-boards/identity.js +40 -0
- package/src/vendor/azure-boards/mcp.js +133 -0
- package/src/vendor/provider-kit/index.js +41 -0
- package/src/vendor/provider-kit/src/adapter.js +23 -0
- package/src/vendor/provider-kit/src/apply.js +207 -0
- package/src/vendor/provider-kit/src/args.js +68 -0
- package/src/vendor/provider-kit/src/assignment.js +277 -0
- package/src/vendor/provider-kit/src/config.js +198 -0
- package/src/vendor/provider-kit/src/credentials.js +404 -0
- package/src/vendor/provider-kit/src/doctor.js +35 -0
- package/src/vendor/provider-kit/src/http.js +63 -0
- package/src/vendor/provider-kit/src/identity.js +93 -0
- package/src/vendor/provider-kit/src/md-compare.js +39 -0
- package/src/vendor/provider-kit/src/md-tables.js +94 -0
- package/src/vendor/provider-kit/src/prompt.js +50 -0
- package/src/vendor/provider-kit/src/released.js +269 -0
- package/src/vendor/provider-kit/src/releases.js +380 -0
- package/src/vendor/provider-kit/src/specs.js +166 -0
- package/src/vendor/provider-kit/src/transport.js +22 -0
- package/src/vendor/sync-core/index.js +93 -0
- package/src/vendor/sync-core/src/base.js +81 -0
- package/src/vendor/sync-core/src/compare.js +338 -0
- package/src/vendor/sync-core/src/dialect.js +51 -0
- package/src/vendor/sync-core/src/fields.js +36 -0
- package/src/vendor/sync-core/src/legacy.js +100 -0
- package/src/vendor/sync-core/src/normalize.js +998 -0
- package/src/vendor/sync-core/src/push.js +85 -0
- package/src/vendor/sync-core/src/retarget.js +278 -0
- package/src/vendor/sync-core/src/task-block.js +308 -0
- package/src/vendor/sync-core/src/verify.js +67 -0
- package/src/vendor/sync-core/src/write.js +191 -0
package/README.md
CHANGED
|
@@ -1,3 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @skitterbyte/skitterspec-azure-boards
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Spec-driven development for Claude Code, with **one-way Azure Boards sync** — a
|
|
4
|
+
superset of [`@skitterbyte/skitterspec`](https://www.npmjs.com/package/@skitterbyte/skitterspec):
|
|
5
|
+
the base filesystem workflow plus `/spec-push` · `/spec-status` · `/spec-claim`
|
|
6
|
+
and the `spec-sync` CLI. The repo is canonical; Azure Boards is a generated
|
|
7
|
+
mirror.
|
|
8
|
+
|
|
9
|
+
Install exactly one distribution — this, the base, or one other superset —
|
|
10
|
+
never two:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
npx @skitterbyte/skitterspec-azure-boards init
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What the sync is
|
|
17
|
+
|
|
18
|
+
- A spec is a **work item**; each phase a **child work item** under a
|
|
19
|
+
parent/child link; tasks ride along as a markdown checklist in the body
|
|
20
|
+
(still a read-only mirror — the repo wins on every push).
|
|
21
|
+
- **Descriptions are markdown**, written verbatim with the format declared on
|
|
22
|
+
the field — checkboxes render natively, and a read-back is byte-comparable.
|
|
23
|
+
- **Work item types and states are the project's own.** The process template
|
|
24
|
+
(Basic, Agile, custom) decides them, so the config names the types and the
|
|
25
|
+
bucket→state map, validated against the process at run time.
|
|
26
|
+
- **One-way**: nothing is read back into the repo. A body edited in Boards is
|
|
27
|
+
reported and replaced on the next push, loudly.
|
|
28
|
+
- **Releases**: CI runs `spec-sync release create <tag>` on a tag push. It
|
|
29
|
+
mints one work item of `releases.workItemType` per release, holding exactly
|
|
30
|
+
the tickets that tag contains by their `Refs: AB#n` trailers;
|
|
31
|
+
`release deployed <tag> <env>` moves that item's state through your envs.
|
|
32
|
+
- **Assignment**: `/spec-start` assigns the work item to you; `/spec-claim`
|
|
33
|
+
hands it on. Identity derives from the PAT (`spec-sync whoami`).
|
|
34
|
+
- **Intake**: adopt a PM-filed work item as a spec's own, routed to `/spec`,
|
|
35
|
+
`/spec-bug` or `/spec-hotfix` by its tags, the original description preserved
|
|
36
|
+
as a comment before the spec replaces it.
|
|
37
|
+
|
|
38
|
+
## Setup
|
|
39
|
+
|
|
40
|
+
You need a PAT with **Work Items (Read & Write)** — add **Identity (Read)** for
|
|
41
|
+
user search. `/spec-azure-setup` interviews you and writes
|
|
42
|
+
`specs/.core/azure-boards.config.json` (org URL, project, types, bucket→state
|
|
43
|
+
map). Keep the token out of the repo — export `AZURE_DEVOPS_EXT_PAT`, or:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
skitterspec-azure-boards spec-sync credentials set
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`skitterspec-azure-boards spec-sync doctor --check-remote` reports readiness
|
|
50
|
+
across every layer, the identity scope included.
|
|
51
|
+
|
|
52
|
+
Not yet shipped for Azure Boards: `retarget`, `reattach`, `stage`,
|
|
53
|
+
`spec-sanitise`.
|
|
54
|
+
|
|
55
|
+
This package's `bin/`, `src/` and `assets/` are composed at build time from the
|
|
56
|
+
skitterspec monorepo — see the repo for sources and development.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
## Spec workflow
|
|
2
|
+
|
|
3
|
+
Spec-driven development runs through the lifecycle **skills** — use them so
|
|
4
|
+
structure and lifecycle stay consistent. The everyday loop is
|
|
5
|
+
**`spec → start → next → commit → complete`**, with **`/spec-diff`** to read what
|
|
6
|
+
a phase changed and `/spec-connect` when you want to test the spec in a browser.
|
|
7
|
+
|
|
8
|
+
**Skills vs commands.** The lifecycle skills are read by Claude, which exercises
|
|
9
|
+
judgment. `/spec-connect` and `/spec-live` are **slash commands** instead — each
|
|
10
|
+
pre-executes one `spec-env` verb and relays it, so only you can run them; a
|
|
11
|
+
skill that wants one will tell you to type it.
|
|
12
|
+
|
|
13
|
+
**Seeing the work.** A phase is built in its own worktree, so `git diff` in your
|
|
14
|
+
terminal answers about the base branch. **`/spec-diff`** renders that worktree's
|
|
15
|
+
diff as a self-contained HTML page you open locally — or publish, and read on a
|
|
16
|
+
phone. The diff never passes through the model, so the page costs no context
|
|
17
|
+
tokens however large it is; the optional written review is the part that costs,
|
|
18
|
+
and it is offered rather than assumed. `/spec-next` writes the page at the end of
|
|
19
|
+
every phase.
|
|
20
|
+
|
|
21
|
+
**Handing the review back.** The page takes marks: tick `✓ accept` per file as
|
|
22
|
+
you read, note anything against a line or a whole file, answer the questions a
|
|
23
|
+
written review asked — then **end it in a decision**. At the end of a phase the
|
|
24
|
+
buttons are `✓ Commit`, `✓ Commit & Continue` (commit, then build the next
|
|
25
|
+
phase), `↺ Request changes` and `… Discuss first`. A spec that has just been
|
|
26
|
+
written gets `✓ Commit & Start` instead — the `commit-start` verdict, which is
|
|
27
|
+
the `commit && /spec-start` you would otherwise type. A **served** page hands
|
|
28
|
+
the pass to the engine, which **holds** it and shows a six-digit code. Committing
|
|
29
|
+
is unavailable while a note is open — you asked for something, so it cannot also
|
|
30
|
+
be fine — and it hands off to your own commit skill (`review.commitWith`,
|
|
31
|
+
`/commit` by default) rather than a copy living here. Fixes come back as
|
|
32
|
+
resolutions, so the next render shows each note struck through with what changed.
|
|
33
|
+
An accept remembers the file's content, so it lapses by itself when that file
|
|
34
|
+
changes again. The marks are information — nothing gates on them; the verdict is
|
|
35
|
+
the one thing you choose, once.
|
|
36
|
+
|
|
37
|
+
**A second opinion, when you want one** (`review.reviewers`, empty by default).
|
|
38
|
+
Point it at a code reviewer that runs from your terminal — CodeRabbit's CLI has
|
|
39
|
+
a free tier and `{ "use": "coderabbit" }` is the whole setup; anything printing
|
|
40
|
+
[rdjsonl](https://github.com/reviewdog/reviewdog) works too — and its findings
|
|
41
|
+
land on the page beside your own notes, each badged with the tool that found it
|
|
42
|
+
and linked to the line it is about. `/spec-next` runs them once per phase;
|
|
43
|
+
`/spec-diff --reviewers` is the mid-phase ask. **They never block a commit** —
|
|
44
|
+
reply to one and your reply does, because then a person has asked for something.
|
|
45
|
+
A reviewer that could not run says `did not run — <why>` rather than passing as
|
|
46
|
+
clean, because on the page you commit from those two must not look alike.
|
|
47
|
+
Adding one sends your diff to whatever the command talks to, which is why the
|
|
48
|
+
list starts empty and `init` never writes one.
|
|
49
|
+
|
|
50
|
+
**Above the verdicts sits one line that is not a verdict.** `▶ Put it live`
|
|
51
|
+
commits the phase, checks the branch out where your dev server can see it, and
|
|
52
|
+
hands you back the same page with the same options — so you can judge the change
|
|
53
|
+
by *using* it, not only by reading it. It clears no gate: you have looked at it
|
|
54
|
+
running and concluded nothing, so the phase still owes an answer.
|
|
55
|
+
|
|
56
|
+
**Every render lists where the page can be read**, labelled, in a fixed order —
|
|
57
|
+
`local`, `network`, `remote` — each either a URL or the one command that turns it
|
|
58
|
+
on, plus a `live:` line saying whether the change is also running. `local` and
|
|
59
|
+
`network` are two doors into one room: the same server and the same waiting
|
|
60
|
+
verdict, so one wait covers both. `remote` is a published page, a second store —
|
|
61
|
+
a verdict there needs `/spec-reviewed`, and it stays off until someone types
|
|
62
|
+
**`/spec-remote-review`**, which toggles it. Publishing is permitted by that
|
|
63
|
+
command, never performed by it: a published page is one this tooling cannot
|
|
64
|
+
remove.
|
|
65
|
+
|
|
66
|
+
**One ending.** Every spec skill finishes with the same block — a verdict
|
|
67
|
+
(`✅` · `⚠️` · `❌` · `⏸`), then a table of the fields that skill declares,
|
|
68
|
+
ending on the one thing to do next. A refusal emits it too, so "nothing
|
|
69
|
+
happened" is reported rather than absent, and `Follow-ups` is always there
|
|
70
|
+
because a recorded `none` is a decision. Skills stay quiet while they run. The
|
|
71
|
+
shape lives in **`.claude/rules/spec-reports.md`**.
|
|
72
|
+
|
|
73
|
+
The skill table, the spec type/folder conventions and the per-spec isolation
|
|
74
|
+
model all live in **`.claude/rules/spec-planning.md`**, the canonical reference
|
|
75
|
+
every spec skill points at. Tailor its per-phase test commands to this stack.
|
|
76
|
+
|
|
77
|
+
**Release gating** *(only when `specs/.core/gating.config.json` exists)* — each
|
|
78
|
+
spec records whether it ships behind a feature flag, as
|
|
79
|
+
`> **Gating:** <flag name>` or `> **Gating:** none: <one-line reason>`. `/spec`,
|
|
80
|
+
`/spec-bug` and `/spec-hotfix` ask; `/spec-review`, `/spec-start` and
|
|
81
|
+
`/spec-complete` report a spec that has no answer, and never block over it.
|
|
82
|
+
`skitterspec gating check` lists them and always exits 0. The point is that the
|
|
83
|
+
question is **on the record**: a missing line is an oversight, a reason is a
|
|
84
|
+
decision. Skitterspec never reads your flag system — it asks and cites the doc
|
|
85
|
+
you point it at. Without that config, none of this appears.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Allow writes on the base branch for this session — bare allows it with no reason, a reason records why, `off` clears it
|
|
3
|
+
argument-hint: "[<reason> | off]"
|
|
4
|
+
allowed-tools: Bash({{exec}} skitterspec spec-env main allow:*)
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
!`{{exec}} skitterspec spec-env main allow $ARGUMENTS`
|
|
8
|
+
|
|
9
|
+
Relay the engine output above verbatim. Add nothing and run nothing else.
|
|
10
|
+
|
|
11
|
+
**Only you can run this, and that is the whole guard.** The main-guard hook
|
|
12
|
+
refuses an `Edit` or a `Write` in the primary checkout while it sits on the base
|
|
13
|
+
branch, and it names two exits: move the work with `/no-spec` or `/spec-start`,
|
|
14
|
+
or have you lift the guard. Claude can take the first exit on its own and can
|
|
15
|
+
never take the second — a guard the model can lift is decoration, which is the
|
|
16
|
+
same line `/spec-reviewed` draws.
|
|
17
|
+
|
|
18
|
+
**Give it a reason.** `/allow-main fixing the lockfile by hand` records why; bare
|
|
19
|
+
records `none given`. The reason is the part a reviewer can argue with, exactly
|
|
20
|
+
as with a `Gating:` header — and it is what you will read when you wonder later
|
|
21
|
+
why the base branch has an uncommitted file on it.
|
|
22
|
+
|
|
23
|
+
**It lapses with the session where it can.** The allow is recorded against this
|
|
24
|
+
session, so it ends when the session does. Where the harness exposes no session
|
|
25
|
+
id it degrades to a repo-wide allow that outlives the session and is cleared only
|
|
26
|
+
by `/allow-main off`; the engine says which kind you got, so do not read a bare
|
|
27
|
+
"allowed" as temporary.
|
|
28
|
+
|
|
29
|
+
**It is machine-local and gitignored.** Unlike `/spec-remote-review`, this writes
|
|
30
|
+
nothing anyone else pulls: it is your decision about your checkout, not the
|
|
31
|
+
project's policy. The project-wide switch is `guards.mainIsLandingZone` in
|
|
32
|
+
`specs/.core/env.config.json`, and turning that off is a different, committed
|
|
33
|
+
decision.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Expose one spec's dev servers on the canonical localhost ports — bare uses the spec you are on, `main` hands them back
|
|
3
|
+
argument-hint: "[<spec> | main]"
|
|
4
|
+
allowed-tools: Bash({{exec}} skitterspec spec-env connect:*)
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
!`{{exec}} skitterspec spec-env connect $ARGUMENTS`
|
|
8
|
+
|
|
9
|
+
Relay the engine output above verbatim. Add nothing and run nothing else.
|
|
10
|
+
|
|
11
|
+
Only if it reports a **canonical port is in use**: say that the main checkout's
|
|
12
|
+
dev server still holds that port, and that stopping it and re-running is the fix
|
|
13
|
+
(the proxy cannot share a port main is bound to).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Put one spec live on the already-running dev server — bare takes the spec you are on, `<spec>` takes that one, `main` hands the instance back
|
|
3
|
+
argument-hint: "[<spec> | main | status | release | abort]"
|
|
4
|
+
allowed-tools: Bash({{exec}} skitterspec spec-env live:*)
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
!`{{exec}} skitterspec spec-env live $ARGUMENTS`
|
|
8
|
+
|
|
9
|
+
Relay the engine output above verbatim. Add nothing and run nothing else.
|
|
10
|
+
|
|
11
|
+
The engine enforces every refusal itself and prints why — a hotfix, a stateful
|
|
12
|
+
spec (`Stack: worktree + docker`), a branch touching migrations, a dirty tree, a
|
|
13
|
+
rebase conflict, or another spec already holding the instance. Do not re-explain
|
|
14
|
+
or work around any of them.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Permit or forbid publishing this spec's review for a reader off your network — bare toggles it, `on`/`off` set it explicitly
|
|
3
|
+
argument-hint: "[on | off]"
|
|
4
|
+
allowed-tools: Bash({{exec}} skitterspec spec-env review allow remote:*)
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
!`{{exec}} skitterspec spec-env review allow remote --set "$ARGUMENTS"`
|
|
8
|
+
|
|
9
|
+
Relay the engine output above verbatim. Add nothing to it.
|
|
10
|
+
|
|
11
|
+
**Then one thing, and only when the engine asked for it.** Where the output
|
|
12
|
+
carries a `review:` and a `publish:` line, typing this command **is** the ask
|
|
13
|
+
to publish that page: the banner named this command beside a review the reader
|
|
14
|
+
could not reach, so the link is what they are waiting for. Publish it exactly
|
|
15
|
+
as `/spec-diff` §6 describes (run the `publish:` command, publish the copy it
|
|
16
|
+
names with a store, write the URL file), then re-emit the review banner with
|
|
17
|
+
all three tiers, `remote` now carrying the URL. A wait already running keeps
|
|
18
|
+
running; do not restart it or the server. If the harness refuses the publish,
|
|
19
|
+
say so in one line and keep the banner as it was, because local and network
|
|
20
|
+
still work.
|
|
21
|
+
|
|
22
|
+
With no `publish:` line, run nothing else. Nothing has a page to publish, or
|
|
23
|
+
the tier was turned off.
|
|
24
|
+
|
|
25
|
+
**Bare toggles.** That is the common case: you are looking at a render whose
|
|
26
|
+
`remote:` line says which way it currently is, and what you want is the other
|
|
27
|
+
one. `on` and `off` are there for when you are not looking at it.
|
|
28
|
+
|
|
29
|
+
**It is per spec.** The engine resolves the spec from the worktree you are
|
|
30
|
+
standing in and records the permission in the gitignored review store, so
|
|
31
|
+
the base branch is untouched and no other spec may publish because of it.
|
|
32
|
+
`skitterspec spec-env review allow remote --project` is the committed,
|
|
33
|
+
every-spec form, for a team that wants it.
|
|
34
|
+
|
|
35
|
+
**A published page is one skitterspec cannot delete**, and a verdict pressed
|
|
36
|
+
on one needs `/spec-reviewed`, because nothing pushes from an artifact's store
|
|
37
|
+
into a conversation.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Move on from a phase without a verdict, with the reason on the record — clears the review gate
|
|
3
|
+
argument-hint: "\"<reason>\""
|
|
4
|
+
allowed-tools: Bash({{exec}} skitterspec spec-env review skip:*)
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
!`{{exec}} skitterspec spec-env review skip "$ARGUMENTS"`
|
|
8
|
+
|
|
9
|
+
Relay the engine output above verbatim. Add nothing and run nothing else.
|
|
10
|
+
|
|
11
|
+
**It refuses with no reason, and that refusal is the feature.** A skip with no
|
|
12
|
+
reason is indistinguishable from nobody having looked — where
|
|
13
|
+
`none: additive, nothing to revert` is a decision a reviewer can argue with. If
|
|
14
|
+
the engine asked for a reason, relay that and stop; do not supply one on the
|
|
15
|
+
operator's behalf.
|
|
16
|
+
|
|
17
|
+
**It is one of exactly two exits**, and the other is a committing verdict on the
|
|
18
|
+
page. Nothing else discharges a phase's obligation — not a mid-run `Continue`,
|
|
19
|
+
not an action, not a render.
|
|
20
|
+
|
|
21
|
+
**Only you can run it.** Like `/allow-main`, this lifts a guard aimed at Claude,
|
|
22
|
+
so Claude must not be able to lift it. `/spec-connect` and `/spec-live` are
|
|
23
|
+
user-only for the milder reason that there is no judgement to apply; this one is
|
|
24
|
+
user-only because of who the guard is for.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Setting up Azure Boards sync (one-way)
|
|
2
|
+
|
|
3
|
+
A start-to-finish guide to getting `/spec-status` and `/spec-push` working
|
|
4
|
+
against a real Azure DevOps organization. Sync is **one-way**: the repo is the
|
|
5
|
+
source of truth and the linked **work item** is a **generated mirror** —
|
|
6
|
+
content is pushed up, never read back or merged. A spec is a work item and each
|
|
7
|
+
phase a **child work item**, with the phase's tasks mirrored into that child's
|
|
8
|
+
description as a markdown checklist (read-only — the repo wins). Covers the
|
|
9
|
+
**Azure side** (credentials, finding your project) that the config reference
|
|
10
|
+
(`azure-boards.config.md`) assumes you already have.
|
|
11
|
+
|
|
12
|
+
> The whole feature is **opt-in**: until `specs/.core/azure-boards.config.json`
|
|
13
|
+
> exists, everything below is inert and the package behaves exactly like the
|
|
14
|
+
> base `@skitterbyte/skitterspec`.
|
|
15
|
+
|
|
16
|
+
Two things about Azure Boards shape everything below:
|
|
17
|
+
|
|
18
|
+
- **Descriptions are MARKDOWN**, written verbatim with the format declared
|
|
19
|
+
beside the field (`multilineFieldsFormat`) — so a read-back is normally the
|
|
20
|
+
pushed text byte for byte, and task checkboxes render natively. A field saved
|
|
21
|
+
as markdown cannot revert to HTML (Azure's rule; fine one-way). Items that
|
|
22
|
+
predate the link read back as HTML and are reduced to text for comparison.
|
|
23
|
+
- **States are written directly** — there is no transition API — and a process
|
|
24
|
+
rule refuses an unknown name only **at write time**, after the description
|
|
25
|
+
has landed. The engine therefore validates every configured state up front
|
|
26
|
+
and refuses loudly (naming what the types DO have) before anything writes.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. Install the package
|
|
31
|
+
|
|
32
|
+
Install the Azure Boards superset (or, if you already run the base or another
|
|
33
|
+
superset, switch — every distribution contains the entire base, so install
|
|
34
|
+
exactly one):
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npx @skitterbyte/skitterspec-azure-boards init
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 2. Credentials — a Personal Access Token
|
|
41
|
+
|
|
42
|
+
Azure DevOps authenticates REST calls with basic auth: an empty username and a
|
|
43
|
+
PAT as the password. Create the token in Azure DevOps under User settings →
|
|
44
|
+
Personal access tokens, scoped **Work Items (Read & Write)** — add
|
|
45
|
+
**Identity (Read)** if you want user search later (work item sync is whole
|
|
46
|
+
without it).
|
|
47
|
+
|
|
48
|
+
Store it in the user-level credentials store (outside every repo, `0600`),
|
|
49
|
+
**in your own terminal — not through an assistant**:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
pnpm exec skitterspec-azure-boards spec-sync credentials set --org-url https://dev.azure.com/<org>
|
|
53
|
+
# Azure DevOps PAT for https://dev.azure.com/<org> (hidden): ●●●●●●●●
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
(`--org-url` keys the store entry before the repo has a config; once
|
|
57
|
+
`azure-boards.config.json` exists it can be omitted.)
|
|
58
|
+
|
|
59
|
+
Or export `AZURE_DEVOPS_EXT_PAT` — the official CLI's variable, so a machine
|
|
60
|
+
set up for `az boards` needs nothing new. The environment wins over the store.
|
|
61
|
+
|
|
62
|
+
> **MCP alternative:** with no credentials at all, the sync skills fall back to
|
|
63
|
+
> Microsoft's official MCP server — remote at `https://mcp.dev.azure.com/{org}`,
|
|
64
|
+
> or local via `@azure-devops/mcp` — authenticated with your Microsoft account.
|
|
65
|
+
> The engine itself never calls MCP tools; the skills fetch and hand results to
|
|
66
|
+
> the engine as files (`--workspace-states`, `--stored`, `--text`).
|
|
67
|
+
|
|
68
|
+
## 3. Configure — run `/spec-azure-setup`
|
|
69
|
+
|
|
70
|
+
With credentials in place, let the skill interview you:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
/spec-azure-setup
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
It asks for the org URL, the project, the work item type names, and the four
|
|
77
|
+
bucket→state names — validating them against the types' real states
|
|
78
|
+
(`spec-sync states`) — then writes `specs/.core/azure-boards.config.json` via
|
|
79
|
+
`spec-sync init-config`. By hand instead:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
pnpm exec skitterspec-azure-boards spec-sync init-config \
|
|
83
|
+
--org-url https://dev.azure.com/<org> \
|
|
84
|
+
--project <project> \
|
|
85
|
+
--state complete=Done
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Commit the file it writes.** Spec worktrees fork from commits, so an
|
|
89
|
+
uncommitted `azure-boards.config.json` is invisible inside every worktree —
|
|
90
|
+
which is where the sync skills actually run — and sync reports itself off
|
|
91
|
+
there.
|
|
92
|
+
|
|
93
|
+
`azure-boards.config.md` (installed beside this file) is the full field
|
|
94
|
+
reference. Two defaults worth knowing: the types are the **Basic** process's
|
|
95
|
+
(`Issue`/`Task`) — an Agile project usually wants `User Story`/`Task`; and
|
|
96
|
+
`cancelled` maps to `Done` because the Basic process carries no Cancelled
|
|
97
|
+
state (Agile's `Removed` is an honest override).
|
|
98
|
+
|
|
99
|
+
**Map buckets to work states, not deployment stages.** The buckets mirror the
|
|
100
|
+
repo's work lifecycle, so To Do / Doing / Done translate honestly — On Test /
|
|
101
|
+
On Live do not: a finished phase would show as deployed before anything was
|
|
102
|
+
released. Deployment position belongs to the release carrier
|
|
103
|
+
(`releases.series[].envs`, moving the release work item) —
|
|
104
|
+
`azure-boards.config.md` → **Choosing your state map** carries the reasoning.
|
|
105
|
+
|
|
106
|
+
## 4. Check — `spec-sync doctor`
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
pnpm exec skitterspec-azure-boards spec-sync doctor --check-remote
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Every row reports `ok`, or names the exact command that fixes it. `missing`
|
|
113
|
+
is an opt-in not taken (fine); `broken` is configured-but-wrong (exit 1).
|
|
114
|
+
`--check-remote` additionally proves the PAT reaches the org, that every
|
|
115
|
+
configured state exists on the types, and whether the PAT can search
|
|
116
|
+
identities (a warn, never broken — only user search degrades without it).
|
|
117
|
+
|
|
118
|
+
## 5. Push a spec
|
|
119
|
+
|
|
120
|
+
`/spec-push <spec>` computes the plan, applies it, stamps the returned ids
|
|
121
|
+
into the spec's frontmatter (`azure_identifier` / `azure_url` /
|
|
122
|
+
`azure_item_id`), verifies the round-trip by words, and records the snapshot
|
|
123
|
+
later drift is measured against. From then on the lifecycle skills keep the
|
|
124
|
+
mirror fresh on their own.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Release smoke checklist
|
|
129
|
+
|
|
130
|
+
Re-runnable, against a real Azure DevOps org — a throwaway org/project is
|
|
131
|
+
ideal (the free tier covers all of it). Run before cutting a release that
|
|
132
|
+
touches the adapter, the dialect or the apply path. `states.json` below is the
|
|
133
|
+
types' state names.
|
|
134
|
+
|
|
135
|
+
1. **Doctor** — every row ok:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
pnpm exec skitterspec-azure-boards spec-sync doctor --check-remote
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
2. **States** — the types' state list:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
pnpm exec skitterspec-azure-boards spec-sync states --json > states.json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
3. **Mint** — on a test spec with at least one phase and a task checklist:
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
pnpm exec skitterspec-azure-boards spec-sync plan <spec> --workspace-states states.json --json > plan.json
|
|
151
|
+
pnpm exec skitterspec-azure-boards spec-sync apply <spec> --plan plan.json
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
In Boards: the work item and its children exist, states match the buckets,
|
|
155
|
+
and the task checklist renders as markdown checkboxes.
|
|
156
|
+
|
|
157
|
+
4. **Update** — edit the spec's prose and a phase's status, then plan + apply
|
|
158
|
+
again: the description is replaced and the child's state moves.
|
|
159
|
+
|
|
160
|
+
5. **Adopt + preserve** — hand-create a work item with a description (in the
|
|
161
|
+
Boards UI, so it is HTML-era), stamp it onto a spec, and preserve before the
|
|
162
|
+
linking push:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
pnpm exec skitterspec-azure-boards spec-sync stamp <spec> --issue AB#123
|
|
166
|
+
pnpm exec skitterspec-azure-boards spec-sync preserve <spec>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The original description appears as a comment headed **Original report**
|
|
170
|
+
(reduced to text from its HTML); a second `preserve` reports "already
|
|
171
|
+
preserved".
|
|
172
|
+
|
|
173
|
+
6. **Verify** — save a read-back (`{ "issue": "<description text>",
|
|
174
|
+
"subIssues": { "<ref>": "<text>" } }`; a ref is the phase file's basename
|
|
175
|
+
without `.md`, e.g. `01-date-logic` — keyed with the extension it reports
|
|
176
|
+
every child as a stale ref) and:
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
pnpm exec skitterspec-azure-boards spec-sync verify <spec> --stored readback.json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
"round-tripped intact", exit 0.
|
|
183
|
+
|
|
184
|
+
7. **Released** — over a range with `Refs:` trailers:
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
pnpm exec skitterspec-azure-boards spec-sync released <tag>..HEAD
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Tickets counted, unreferenced commits reported, an earlier tracker's
|
|
191
|
+
trailers counted as historical, bookkeeping ignored.
|
|
192
|
+
|
|
193
|
+
8. **Release carrier** — with a `releases.series` configured and a tag pushed:
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
pnpm exec skitterspec-azure-boards spec-sync release create <tag>
|
|
197
|
+
pnpm exec skitterspec-azure-boards spec-sync release create <tag> --apply
|
|
198
|
+
pnpm exec skitterspec-azure-boards spec-sync release deployed <tag> staged --apply
|
|
199
|
+
pnpm exec skitterspec-azure-boards spec-sync release complete <tag> --apply
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
In Boards: one `releases.workItemType` item titled by the tag, the range's
|
|
203
|
+
tickets Related-linked with one comment each, the item's state moving
|
|
204
|
+
through the envs and closing on complete. A retry of `create --apply`
|
|
205
|
+
reports "unchanged", never a duplicate.
|
|
206
|
+
|
|
207
|
+
9. **Drift** — edit the item's description **in Boards**, fetch the item JSON,
|
|
208
|
+
and:
|
|
209
|
+
|
|
210
|
+
```sh
|
|
211
|
+
pnpm exec skitterspec-azure-boards spec-sync status <spec> --remote item.json
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The edit is reported; the next apply replaces it and says so loudly.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"azure": {
|
|
3
|
+
"orgUrl": "https://dev.azure.com/your-org",
|
|
4
|
+
"project": "your-project",
|
|
5
|
+
"workItemType": "Issue",
|
|
6
|
+
"childType": "Task",
|
|
7
|
+
"areaPath": ""
|
|
8
|
+
},
|
|
9
|
+
"states": {
|
|
10
|
+
"backlog": "To Do",
|
|
11
|
+
"in-progress": "Doing",
|
|
12
|
+
"complete": "Done",
|
|
13
|
+
"cancelled": "Done"
|
|
14
|
+
},
|
|
15
|
+
"intake": {
|
|
16
|
+
"tag": "",
|
|
17
|
+
"bugTags": ["bug"],
|
|
18
|
+
"hotfixTags": []
|
|
19
|
+
},
|
|
20
|
+
"auth": {
|
|
21
|
+
"tokenEnv": "AZURE_DEVOPS_EXT_PAT"
|
|
22
|
+
},
|
|
23
|
+
"refs": {
|
|
24
|
+
"foreignPatterns": []
|
|
25
|
+
},
|
|
26
|
+
"releases": {
|
|
27
|
+
"workItemType": "Release",
|
|
28
|
+
"series": [
|
|
29
|
+
{
|
|
30
|
+
"tagPrefix": "v",
|
|
31
|
+
"envs": { "staged": "Staged", "published": "Published" }
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# `azure-boards.config.json` — field reference
|
|
2
|
+
|
|
3
|
+
The committed, shareable half of Azure Boards sync. It holds ids, names and
|
|
4
|
+
env-var NAMES — never a credential. Credentials live in the user-level store
|
|
5
|
+
(`skitterspec-azure-boards spec-sync credentials set`) or the environment.
|
|
6
|
+
|
|
7
|
+
| Key | Meaning | Default |
|
|
8
|
+
|-----|---------|---------|
|
|
9
|
+
| `azure.orgUrl` | The organization (`https://dev.azure.com/<org>`). Also keys the credentials-store entry. | `""` |
|
|
10
|
+
| `azure.project` | The project spec work items land in. | `""` |
|
|
11
|
+
| `azure.workItemType` | The work item type a spec mints as. Types are process-defined (Basic: `Issue`; Agile: `User Story`; custom anything), so this is config, not a constant. | `"Issue"` |
|
|
12
|
+
| `azure.childType` | The type a phase mints as, linked parent/child under the spec's item. | `"Task"` |
|
|
13
|
+
| `azure.areaPath` | Optional area path for minted items; empty means the project default. | `""` |
|
|
14
|
+
| `mapping.specFolder` / `mapping.phases` / `mapping.tasks` | Spec → work item, phase → child item, tasks → markdown checklist. Same modes as the other providers (`subissue` \| `deferred` \| `inline`; `checklist` \| `none`). | `issue` / `subissue` / `checklist` |
|
|
15
|
+
| `states.<bucket>` | The Boards STATE name for each lifecycle bucket, per the project's own process. Moves are direct `System.State` writes; a state the process refuses is a named refusal listing the states the type has. `cancelled` defaults to `Done` because the Basic process carries no Cancelled state. | see example |
|
|
16
|
+
| `intake.tag` / `intake.bugTags` / `intake.hotfixTags` | Intake routes an adopted work item by its tags (Boards has tags, not labels): the inbox filter, and the routes to `/spec-bug` and `/spec-hotfix`. Empty lists route nothing. | `""` / `["bug"]` / `[]` |
|
|
17
|
+
| `auth.tokenEnv` | The env-var NAME for the PAT — the official CLI's by default, so a machine set up for `az boards` needs nothing new. Work Items (Read & Write) covers everything but identity search, which wants Identity (Read). | `AZURE_DEVOPS_EXT_PAT` |
|
|
18
|
+
| `refs.foreignPatterns` | Ref shapes that are KNOWN HISTORY rather than unreferenced commits — a repo migrating off another tracker lists its old key here (e.g. `"ERQ-\\d+"`). | `[]` |
|
|
19
|
+
| `releases.workItemType` / `releases.series` | The release seam's carrier: one work item of this type per release, found by title, its state moved through each series' `envs` map by `release deployed`. | `"Release"` / `[]` |
|
|
20
|
+
| `apply.transport` | `api` \| `mcp`, or empty for "api when credentials resolve, mcp when they don't". | `""` |
|
|
21
|
+
| `sync.baseDir` | Where last-pushed snapshots live. | `specs/.core/azure-base` |
|
|
22
|
+
| `sync.fieldOwnership` | Which projection fields push (`description`, `subIssues`, `workflowState`, `assignee`; `"none"` declines one). | all `push` |
|
|
23
|
+
| `release.stages` / `release.ignorePaths` | The deployment ladder and bookkeeping path prefixes, exactly as the other providers define them. | `[]` / `specs/`, changelogs |
|
|
24
|
+
|
|
25
|
+
Descriptions are stored as MARKDOWN: the adapter writes the projection's
|
|
26
|
+
markdown verbatim and declares the format beside the field
|
|
27
|
+
(`multilineFieldsFormat`), so a read-back is byte-comparable and task
|
|
28
|
+
checkboxes render natively. A field saved as markdown cannot revert to HTML —
|
|
29
|
+
Azure's rule, and fine one-way: this provider never writes HTML. Work items
|
|
30
|
+
that predate the link (every intake-adopted item) come back as HTML and are
|
|
31
|
+
reduced to text for comparison and preservation, never written back.
|
|
32
|
+
|
|
33
|
+
A box ticked in Boards is still overwritten on the next push — the mirror is
|
|
34
|
+
read-only, exactly as the other providers' are.
|
|
35
|
+
|
|
36
|
+
## Choosing your state map
|
|
37
|
+
|
|
38
|
+
Map the four buckets to **work** states, never to deployment stages — the
|
|
39
|
+
same rule the Jira reference explains at length. The repo learns where work
|
|
40
|
+
**is** as it happens, but what **shipped** only when a release is cut, so a
|
|
41
|
+
pipeline state driven from the spec lifecycle is wrong the moment it moves.
|
|
42
|
+
Deployment position belongs to the release seam (`releases.series[].envs`,
|
|
43
|
+
moving the release work item), not to each ticket's state.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mode": "worktree",
|
|
3
|
+
"worktree": {
|
|
4
|
+
"root": "../{repo}-wt",
|
|
5
|
+
"folderPattern": "{slug}"
|
|
6
|
+
},
|
|
7
|
+
"docker": {
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"composeFile": "docker-compose.yml",
|
|
10
|
+
"projectNamePattern": "{repoSlug}_{slug}",
|
|
11
|
+
"portBase": 3000,
|
|
12
|
+
"portsPerSpec": 10,
|
|
13
|
+
"envFile": ".env",
|
|
14
|
+
"backupCommand": ""
|
|
15
|
+
},
|
|
16
|
+
"seedFiles": {
|
|
17
|
+
"mode": "symlink",
|
|
18
|
+
"files": [
|
|
19
|
+
".env"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"setup": [],
|
|
23
|
+
"dev": [],
|
|
24
|
+
"proxy": {
|
|
25
|
+
"enabled": true,
|
|
26
|
+
"host": "127.0.0.1"
|
|
27
|
+
},
|
|
28
|
+
"registry": ".spec-env/registry.json",
|
|
29
|
+
"branch": {
|
|
30
|
+
"pattern": "{type}/{slug}",
|
|
31
|
+
"identifierField": "azure_identifier"
|
|
32
|
+
},
|
|
33
|
+
"spec": {
|
|
34
|
+
"companionPaths": [
|
|
35
|
+
"specs/.core/azure-base/{identifier}.base.json"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"baseBranch": "",
|
|
39
|
+
"guards": {
|
|
40
|
+
"refuseTeardownIfDirty": true,
|
|
41
|
+
"refuseTeardownIfUnpushed": true
|
|
42
|
+
},
|
|
43
|
+
"review": {
|
|
44
|
+
"reader": "detect",
|
|
45
|
+
"servePort": "auto",
|
|
46
|
+
"serve": "always",
|
|
47
|
+
"allowNetwork": true,
|
|
48
|
+
"allowRemote": false,
|
|
49
|
+
"commitWith": "/commit",
|
|
50
|
+
"required": true,
|
|
51
|
+
"reviewers": []
|
|
52
|
+
},
|
|
53
|
+
"live": {
|
|
54
|
+
"migrations": []
|
|
55
|
+
}
|
|
56
|
+
}
|