@skitterbyte/skitterspec-jira 0.0.0-stage → 2.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 +1266 -0
- package/README.md +44 -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 +192 -0
- package/assets/core/env.config.json.example +56 -0
- package/assets/core/env.config.md +606 -0
- package/assets/core/gating.config.json.example +4 -0
- package/assets/core/gating.config.md +81 -0
- package/assets/core/jira.config.json.example +18 -0
- package/assets/core/jira.config.md +88 -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 +96 -0
- package/assets/rules/negative-checks.md +69 -0
- package/assets/rules/offered-unblocks.md +100 -0
- package/assets/rules/spec-planning.md +702 -0
- package/assets/rules/spec-reports.md +572 -0
- package/assets/skills/no-spec/SKILL.md +249 -0
- package/assets/skills/spec/SKILL.md +714 -0
- package/assets/skills/spec-bug/SKILL.md +585 -0
- package/assets/skills/spec-cancel/SKILL.md +261 -0
- package/assets/skills/spec-complete/SKILL.md +451 -0
- package/assets/skills/spec-diff/SKILL.md +870 -0
- package/assets/skills/spec-hotfix/SKILL.md +607 -0
- package/assets/skills/spec-init/SKILL.md +178 -0
- package/assets/skills/spec-jira-setup/SKILL.md +221 -0
- package/assets/skills/spec-list/SKILL.md +173 -0
- package/assets/skills/spec-next/SKILL.md +678 -0
- package/assets/skills/spec-push/SKILL.md +306 -0
- package/assets/skills/spec-review/SKILL.md +256 -0
- package/assets/skills/spec-reviewed/SKILL.md +284 -0
- package/assets/skills/spec-start/SKILL.md +496 -0
- package/assets/skills/spec-status/SKILL.md +114 -0
- package/assets/skills/spec-sync/SKILL.md +220 -0
- package/assets/skills/spec-to-main/SKILL.md +141 -0
- package/bin/skitterspec-jira.js +68 -0
- package/package.json +40 -4
- package/src/cli.js +5967 -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 +850 -0
- package/src/env/dev.js +63 -0
- package/src/env/glob.js +34 -0
- package/src/env/hooks.js +226 -0
- package/src/env/hotfix.js +413 -0
- package/src/env/hotfixland.js +302 -0
- package/src/env/integrate.js +167 -0
- package/src/env/live.js +479 -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 +2622 -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/jira/adf.js +414 -0
- package/src/vendor/jira/api.js +295 -0
- package/src/vendor/jira/cli-sync.js +1252 -0
- package/src/vendor/jira/commands.js +44 -0
- package/src/vendor/jira/config.js +286 -0
- package/src/vendor/jira/credentials.js +108 -0
- package/src/vendor/jira/dialect.js +45 -0
- package/src/vendor/jira/mcp.js +129 -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 +251 -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/list-assignee.js +71 -0
- package/src/vendor/provider-kit/src/list-rows.js +35 -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 +219 -0
- package/src/vendor/provider-kit/src/specs.js +248 -0
- package/src/vendor/provider-kit/src/transport.js +22 -0
- package/src/vendor/sync-core/index.js +92 -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 +971 -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/src/env/dev.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure planner for host dev-process supervision (`spec-env dev up|down`).
|
|
5
|
+
*
|
|
6
|
+
* Given a resolved spec and its allocated slot, `planDev` computes — for each
|
|
7
|
+
* configured `dev` process — its resolved port, the env var to inject, the log
|
|
8
|
+
* and pid file paths, the expanded health URL, and the canonical `frontPort` a
|
|
9
|
+
* later proxy (Phase 2) routes to it. No side effects: the CLI's `supervise.js`
|
|
10
|
+
* does the spawning/killing; this stays unit-testable with no processes.
|
|
11
|
+
*
|
|
12
|
+
* Port math: process `i` in the slot's block gets `portBase + slot*portsPerSpec
|
|
13
|
+
* + i` (reusing the same block the Docker stack draws from — see registry.js).
|
|
14
|
+
* So host dev servers get a reserved block even on a worktree-only spec.
|
|
15
|
+
*
|
|
16
|
+
* Log/pid files live beside the registry (default `.spec-env/`) and are keyed by
|
|
17
|
+
* the spec **folder** (not the bare slug) so a `feat-foo`/`bug-foo` pair can't
|
|
18
|
+
* collide — matching how the registry keys slots.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const path = require('node:path')
|
|
22
|
+
const { portOffset } = require('./registry.js')
|
|
23
|
+
const { expandTokens } = require('./resolve.js')
|
|
24
|
+
|
|
25
|
+
// The state dir (logs + pids) sits beside the registry file, e.g. `.spec-env`.
|
|
26
|
+
function stateDir(config) {
|
|
27
|
+
return path.posix.dirname(config.registry) || '.spec-env'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Plan the host dev processes for `spec` at `slot`.
|
|
32
|
+
*
|
|
33
|
+
* @returns {object} { slot, portOffset, procs: [{ name, command, port, portVar,
|
|
34
|
+
* env, frontPort, logFile, pidFile, health }] } — logFile/pidFile are paths
|
|
35
|
+
* relative to the primary checkout root (the CLI resolves them).
|
|
36
|
+
*/
|
|
37
|
+
function planDev(spec, slot, config) {
|
|
38
|
+
const base = portOffset(slot, config)
|
|
39
|
+
const dir = stateDir(config)
|
|
40
|
+
const procs = (config.dev || []).map((entry, i) => {
|
|
41
|
+
const port = base + i
|
|
42
|
+
const tokens = {
|
|
43
|
+
[entry.portVar]: String(port),
|
|
44
|
+
port: String(port),
|
|
45
|
+
slug: spec.slug,
|
|
46
|
+
name: entry.name,
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
name: entry.name,
|
|
50
|
+
command: expandTokens(entry.command, tokens),
|
|
51
|
+
port,
|
|
52
|
+
portVar: entry.portVar,
|
|
53
|
+
env: { [entry.portVar]: String(port) },
|
|
54
|
+
frontPort: typeof entry.frontPort === 'number' ? entry.frontPort : null,
|
|
55
|
+
logFile: `${dir}/logs/${spec.folder}-${entry.name}.log`,
|
|
56
|
+
pidFile: `${dir}/pids/${spec.folder}-${entry.name}.pid`,
|
|
57
|
+
health: entry.health ? expandTokens(entry.health, tokens) : null,
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
return { slot, portOffset: base, procs }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { planDev, stateDir }
|
package/src/env/glob.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Restricted glob matching for repo-relative paths, shared by the live-overlay
|
|
5
|
+
* migration check and the conditional `afterLand` entries. Zero-dependency.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Translate a restricted glob into an anchored RegExp: `**` matches across path
|
|
9
|
+
// separators (with an optional trailing `/`), `*` matches within a segment, `?`
|
|
10
|
+
// a single non-separator char. Enough for repo-path globs, no dependency.
|
|
11
|
+
function globToRegExp(glob) {
|
|
12
|
+
let re = ''
|
|
13
|
+
for (let i = 0; i < glob.length; i++) {
|
|
14
|
+
const ch = glob[i]
|
|
15
|
+
if (ch === '*') {
|
|
16
|
+
if (glob[i + 1] === '*') {
|
|
17
|
+
re += '.*'
|
|
18
|
+
i++
|
|
19
|
+
if (glob[i + 1] === '/') i++ // consume the `/` in `**/`
|
|
20
|
+
} else {
|
|
21
|
+
re += '[^/]*'
|
|
22
|
+
}
|
|
23
|
+
} else if (ch === '?') {
|
|
24
|
+
re += '[^/]'
|
|
25
|
+
} else if ('.+^${}()|[]\\/'.includes(ch)) {
|
|
26
|
+
re += '\\' + ch
|
|
27
|
+
} else {
|
|
28
|
+
re += ch
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return new RegExp('^' + re + '$')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { globToRegExp }
|
package/src/env/hooks.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Register the review-gate hook in the project's Claude Code settings.
|
|
5
|
+
*
|
|
6
|
+
* `.claude/settings.json` rather than `settings.local.json`, and the difference
|
|
7
|
+
* is the point: the trusted-worktree entry is an absolute path and therefore
|
|
8
|
+
* one machine's business, while this is the project's policy — a phase that
|
|
9
|
+
* ended owes a verdict — and it should reach everyone who clones the repo. The
|
|
10
|
+
* command is written with `${CLAUDE_PROJECT_DIR}` so it stays true wherever the
|
|
11
|
+
* checkout lives, worktrees included.
|
|
12
|
+
*
|
|
13
|
+
* Conservative in the same way `trust.js` is: every existing key is preserved,
|
|
14
|
+
* a file it cannot parse is left exactly as it is, and re-running changes
|
|
15
|
+
* nothing once the entry is there. Callers own the reporting.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('node:fs')
|
|
19
|
+
const path = require('node:path')
|
|
20
|
+
|
|
21
|
+
// `.cjs`, and the extension is load-bearing. This file is copied INTO the target
|
|
22
|
+
// project, where that project's `package.json` decides how node parses a `.js` —
|
|
23
|
+
// so the CommonJS script shipped as `review-gate.js` died on its own first
|
|
24
|
+
// `require` in every `"type": "module"` project, printing a stack trace over
|
|
25
|
+
// every Bash tool call. `.cjs` settles the parse mode at the file, independently
|
|
26
|
+
// of the one file skitterspec does not control.
|
|
27
|
+
const HOOK_STEM = '.claude/hooks/review-gate'
|
|
28
|
+
const HOOK_SCRIPT = `${HOOK_STEM}.cjs`
|
|
29
|
+
const HOOK_COMMAND = `node "\${CLAUDE_PROJECT_DIR}/${HOOK_SCRIPT}"`
|
|
30
|
+
|
|
31
|
+
// Our script under ANY extension, so a registration naming the retired `.js` is
|
|
32
|
+
// recognised as ours and migrated rather than duplicated. The trailing lookahead
|
|
33
|
+
// is the boundary: without it `review-gate-extra.js` — somebody else's hook —
|
|
34
|
+
// matches on the stem and gets silently rewritten.
|
|
35
|
+
const HOOK_SCRIPT_RE = /[.]claude[/\\]hooks[/\\]review-gate(?:[.][A-Za-z0-9]+)?(?![\w.-])/
|
|
36
|
+
// Seconds. The engine call behind this is one git-free read of a small JSON
|
|
37
|
+
// file, so anything approaching this is a wedge rather than slow work — and a
|
|
38
|
+
// hook that times out fails OPEN, which is the answer we want for a wedge.
|
|
39
|
+
const HOOK_TIMEOUT = 10
|
|
40
|
+
const MATCHER = 'Bash'
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Every hook this package registers, as data.
|
|
44
|
+
*
|
|
45
|
+
* ONE LIST, so a second hook cannot arrive as a second copy of the merge logic
|
|
46
|
+
* below — which is where every property this file has earned would have to be
|
|
47
|
+
* re-derived and one of them forgotten. Each entry supplies only what differs:
|
|
48
|
+
* the script's stem, the tools it matches, and its timeout.
|
|
49
|
+
*
|
|
50
|
+
* The `re` for each is built from its stem with the same boundary lookahead the
|
|
51
|
+
* review gate's has, so `main-guard-extra.js` — somebody else's hook — is not
|
|
52
|
+
* silently rewritten as ours.
|
|
53
|
+
*/
|
|
54
|
+
function hookStemRe(stem) {
|
|
55
|
+
const name = stem.split('/').pop()
|
|
56
|
+
return new RegExp(`[.]claude[/\\\\]hooks[/\\\\]${name}(?:[.][A-Za-z0-9]+)?(?![\\w.-])`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const HOOKS = [
|
|
60
|
+
{
|
|
61
|
+
key: 'review-gate',
|
|
62
|
+
stem: HOOK_STEM,
|
|
63
|
+
matcher: MATCHER,
|
|
64
|
+
timeout: HOOK_TIMEOUT,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
// Refuses an Edit/Write in the primary checkout while it is on the base
|
|
68
|
+
// branch. A different matcher from the review gate's, because it guards a
|
|
69
|
+
// different moment: the first write, before any work exists.
|
|
70
|
+
key: 'main-guard',
|
|
71
|
+
stem: '.claude/hooks/main-guard',
|
|
72
|
+
matcher: 'Edit|Write|NotebookEdit|MultiEdit',
|
|
73
|
+
timeout: HOOK_TIMEOUT,
|
|
74
|
+
},
|
|
75
|
+
].map((h) => ({ ...h, script: `${h.stem}.cjs`, re: hookStemRe(h.stem) }))
|
|
76
|
+
|
|
77
|
+
function isObject(value) {
|
|
78
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function settingsPath(dir) {
|
|
82
|
+
return path.join(dir, '.claude', 'settings.json')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function writeSettings(file, settings) {
|
|
86
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
87
|
+
fs.writeFileSync(file, JSON.stringify(settings, null, 2) + '\n')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function commandFor(script) {
|
|
91
|
+
return `node "\${CLAUDE_PROJECT_DIR}/${script}"`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function hookEntry(hook = HOOKS[0]) {
|
|
95
|
+
return {
|
|
96
|
+
matcher: hook.matcher,
|
|
97
|
+
hooks: [{ type: 'command', command: commandFor(hook.script), timeout: hook.timeout }],
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Is our hook already registered under `PreToolUse`? Pure.
|
|
103
|
+
*
|
|
104
|
+
* Matched on the SCRIPT PATH, not on the whole command string. An operator who
|
|
105
|
+
* added a timeout, wrapped the invocation, or changed the interpreter has
|
|
106
|
+
* registered our hook their way — re-adding a second copy beside theirs would
|
|
107
|
+
* run it twice and look like a bug in the gate.
|
|
108
|
+
*/
|
|
109
|
+
function alreadyRegistered(preToolUse, hook = HOOKS[0]) {
|
|
110
|
+
return registeredHooks(preToolUse, hook).length > 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Every hook object under `PreToolUse` whose command names our script, whatever
|
|
114
|
+
// extension it names it under. Pure.
|
|
115
|
+
function registeredHooks(preToolUse, hook = HOOKS[0]) {
|
|
116
|
+
if (!Array.isArray(preToolUse)) return []
|
|
117
|
+
const out = []
|
|
118
|
+
for (const group of preToolUse) {
|
|
119
|
+
if (!isObject(group) || !Array.isArray(group.hooks)) continue
|
|
120
|
+
for (const h of group.hooks) {
|
|
121
|
+
if (isObject(h) && typeof h.command === 'string' && hook.re.test(h.command)) out.push(h)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return out
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Ensure ONE hook is registered in an already-parsed settings object.
|
|
129
|
+
* Mutates `parsed` in place when it migrates a stale path; returns
|
|
130
|
+
* `{ changed, reason }` with the same vocabulary as the wrapper below.
|
|
131
|
+
*
|
|
132
|
+
* Split out so a second hook could be added without a second copy of this
|
|
133
|
+
* logic — which is where every property this file has earned (preserve unknown
|
|
134
|
+
* keys, match on the script PATH, migrate in place, stay idempotent) would have
|
|
135
|
+
* had to be re-derived, and one of them forgotten.
|
|
136
|
+
*/
|
|
137
|
+
function ensureOneHook(parsed, hook) {
|
|
138
|
+
const hooks = isObject(parsed.hooks) ? parsed.hooks : {}
|
|
139
|
+
const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : []
|
|
140
|
+
|
|
141
|
+
// Registered already — but possibly under a path that no longer exists, since
|
|
142
|
+
// the script's extension changed. Rewrite the path IN PLACE rather than
|
|
143
|
+
// replacing the command: an operator who wrapped our hook, added a flag or
|
|
144
|
+
// changed the interpreter has registered it their way, and the thing that
|
|
145
|
+
// moved is the file, not their command. Adding a fresh entry beside theirs
|
|
146
|
+
// would run the gate twice and read as a bug in the gate; leaving the old one
|
|
147
|
+
// alone would aim the harness at a file this same upgrade retires.
|
|
148
|
+
const registered = registeredHooks(preToolUse, hook)
|
|
149
|
+
if (registered.length) {
|
|
150
|
+
const stale = registered.filter((h) => !h.command.includes(hook.script))
|
|
151
|
+
if (!stale.length) return { changed: false, reason: 'present' }
|
|
152
|
+
for (const h of stale) h.command = h.command.replace(hook.re, hook.script)
|
|
153
|
+
return { changed: true, reason: 'migrated' }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
parsed.hooks = { ...hooks, PreToolUse: [...preToolUse, hookEntry(hook)] }
|
|
157
|
+
return { changed: true, reason: 'added' }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Ensure every hook this package ships is registered in `dir`'s project
|
|
162
|
+
* settings. Idempotent and non-destructive. Returns `{ changed, reason }`:
|
|
163
|
+
* - `created` — no settings file; one was written
|
|
164
|
+
* - `added` — merged into an existing file
|
|
165
|
+
* - `migrated` — registered under a retired path; the path was rewritten
|
|
166
|
+
* - `present` — already registered (no write)
|
|
167
|
+
* - `malformed` — the file exists but is not parseable JSON (left untouched)
|
|
168
|
+
*
|
|
169
|
+
* WITH SEVERAL HOOKS THE REASON IS THE STRONGEST ONE that applied, in the order
|
|
170
|
+
* added > migrated > present. A run that added one hook and found the other
|
|
171
|
+
* already there did change the file, and reporting `present` for it would tell
|
|
172
|
+
* an operator nothing happened when something did.
|
|
173
|
+
*/
|
|
174
|
+
function ensureHooks(dir) {
|
|
175
|
+
const file = settingsPath(dir)
|
|
176
|
+
|
|
177
|
+
let raw
|
|
178
|
+
try {
|
|
179
|
+
raw = fs.readFileSync(file, 'utf-8')
|
|
180
|
+
} catch (error) {
|
|
181
|
+
if (error.code === 'ENOENT') {
|
|
182
|
+
writeSettings(file, { hooks: { PreToolUse: HOOKS.map((h) => hookEntry(h)) } })
|
|
183
|
+
return { changed: true, reason: 'created' }
|
|
184
|
+
}
|
|
185
|
+
throw error
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let parsed
|
|
189
|
+
try {
|
|
190
|
+
parsed = JSON.parse(raw)
|
|
191
|
+
} catch {
|
|
192
|
+
// Never rewrite a settings file we could not read. It is the operator's
|
|
193
|
+
// config and everything else in it would be lost.
|
|
194
|
+
return { changed: false, reason: 'malformed' }
|
|
195
|
+
}
|
|
196
|
+
if (!isObject(parsed)) return { changed: false, reason: 'malformed' }
|
|
197
|
+
|
|
198
|
+
const reasons = HOOKS.map((h) => ensureOneHook(parsed, h))
|
|
199
|
+
const changed = reasons.some((r) => r.changed)
|
|
200
|
+
if (changed) writeSettings(file, parsed)
|
|
201
|
+
|
|
202
|
+
for (const rank of ['added', 'migrated', 'present']) {
|
|
203
|
+
if (reasons.some((r) => r.reason === rank)) return { changed, reason: rank }
|
|
204
|
+
}
|
|
205
|
+
return { changed, reason: 'present' }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// The name this shipped under when there was one hook. Kept as an alias rather
|
|
209
|
+
// than renamed away, because it is what an installed `init.js` from an older
|
|
210
|
+
// version calls — and a rename that breaks an upgrade path is not a rename, it
|
|
211
|
+
// is a removal.
|
|
212
|
+
const ensureReviewGateHook = ensureHooks
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
ensureHooks,
|
|
216
|
+
ensureReviewGateHook,
|
|
217
|
+
alreadyRegistered,
|
|
218
|
+
registeredHooks,
|
|
219
|
+
hookEntry,
|
|
220
|
+
settingsPath,
|
|
221
|
+
HOOKS,
|
|
222
|
+
HOOK_STEM,
|
|
223
|
+
HOOK_SCRIPT,
|
|
224
|
+
HOOK_SCRIPT_RE,
|
|
225
|
+
HOOK_COMMAND,
|
|
226
|
+
}
|