From de6472680df05c4eaae19ac88fa1f74845e6552f Mon Sep 17 00:00:00 2001 From: Evan Jacobs Date: Fri, 24 Jul 2026 11:27:22 -0400 Subject: [PATCH 1/7] Add Cursor sessionStart harness adapter Wire `devctl hook install --harness cursor` into ~/.cursor/hooks.json so Agent sessions get the same context Claude Code already gets. Resolve the running binary via _NSGetExecutablePath so install from a bare PATH name does not write a cwd-relative command. --- BACKLOG.md | 2 +- CONTRIBUTING.md | 4 +- README.md | 3 +- Sources/devctl/CLI.swift | 32 +++++-- Sources/devctl/HookSupport.swift | 142 +++++++++++++++++++++++++++++- Sources/devctl/LaunchdAdmin.swift | 2 +- docs/cli-contract.md | 2 +- 7 files changed, 168 insertions(+), 19 deletions(-) diff --git a/BACKLOG.md b/BACKLOG.md index 9bc9b11..4bc0240 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -2,7 +2,7 @@ Open work only; entries are removed by the change that resolves them. -- DMG distribution with one-click install (Evan, 2026-07-19): a simple dmg builder (scripts/make-dmg.sh from the existing bundle pipeline; hdiutil + a drag-to-Applications layout) where first launch of devctl.app completes setup itself: install the CLI/daemon binaries from the app bundle's Resources, run daemon install, and auto-detect installed agent harnesses (Claude Code via ~/.claude presence → offer/perform `hook install --harness claude`), with a small first-run panel reporting what was set up. Requires Developer ID signing + notarization (already backlogged) to be download-safe; sequence the two together. +- DMG distribution with one-click install (Evan, 2026-07-19): a simple dmg builder (scripts/make-dmg.sh from the existing bundle pipeline; hdiutil + a drag-to-Applications layout) where first launch of devctl.app completes setup itself: install the CLI/daemon binaries from the app bundle's Resources, run daemon install, and auto-detect installed agent harnesses (Claude Code via ~/.claude presence → offer/perform `hook install --harness claude`; Cursor via ~/.cursor presence → `hook install --harness cursor`), with a small first-run panel reporting what was set up. Requires Developer ID signing + notarization (already backlogged) to be download-safe; sequence the two together. - Orphan re-adoption without the bounce: after a daemon crash, re-adopt live orphan servers (pid + start-time match, resume spool tailing) instead of group-kill + restart. Blocked on: exit codes are unknowable for non-children; needs a design for degraded forensics. - Reverse proxy on :80/:443 routing by host signature, making ports disappear from `*.localhost` URLs (Valet/Herd territory). - MenuBarExtraAccess (orchetect) if `.window` presentation quirks bite in practice. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3abd4a4..596b658 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ devctl is a personal tool first; issues and patches are welcome all the same. Re The session-context payload is harness-agnostic: `devctl context` prints a fenced plain-text block describing the current project's servers, and `devctl statusline` prints a one-line presence summary from statusline stdin JSON. Wiring those into a harness is the only per-harness work. 1. Conform to `HarnessAdapter` in `Sources/devctl/HookSupport.swift`: a `name` (the `--harness` value) and an idempotent `install(devctlPath:)` that merges a session-start hook into the harness's settings without clobbering what is already there. -2. If the harness wants a structured payload (as Claude Code does with `hookSpecificOutput.additionalContext`), add a hidden subcommand like `HookClaudeSessionStart` that adapts `AgentContext.render` output to that shape. Keep the guarantees: exit 0 always, fast, silent when there is nothing to say, never auto-starting the daemon, and never emitting raw log lines or command strings (child output and committed configs are attacker-influenceable). +2. If the harness wants a structured payload (as Claude Code does with `hookSpecificOutput.additionalContext`, or Cursor with `{additional_context}`), add a hidden subcommand like `HookClaudeSessionStart` / `HookCursorSessionStart` that adapts `AgentContext.render` output to that shape. Keep the guarantees: exit 0 always, fast, silent when there is nothing to say, never auto-starting the daemon, and never emitting raw log lines or command strings (child output and committed configs are attacker-influenceable). Resolve the session directory via `HookSessionCwd` (Cursor: `workspace_roots` / `CURSOR_PROJECT_DIR`; Claude: `cwd`). 3. Register the adapter in `harnessAdapters` and document the harness in `docs/cli-contract.md` under `devctl hook install`. -A harness that only supports plain-text injection needs no adapter code at all: point its hook at `devctl context`. +A harness that only supports plain-text injection needs no adapter code at all: point its hook at `devctl context`. Shipped adapters today: `claude`, `cursor`. diff --git a/README.md b/README.md index d267609..272fe4f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ cd your-project devctl register --name web --cmd bun --cmd run --cmd dev --port 3000 devctl ensure web # idempotent: healthy is a no-op devctl why web # root cause when something breaks -devctl hook install # Claude Code sessions rediscover servers automatically +devctl hook install --harness cursor # Cursor Agent sessions rediscover servers automatically +devctl hook install --harness claude # same for Claude Code (default harness) ``` Or commit a `devservers.json` at the project root (multiple servers, dependencies, healthchecks, `*.localhost` host signatures, multi-headed proxies, lifecycle playbooks); `devctl up` brings the whole project up in dependency order. The full CLI contract lives in [docs/cli-contract.md](./docs/cli-contract.md). diff --git a/Sources/devctl/CLI.swift b/Sources/devctl/CLI.swift index 0a85971..fee22aa 100644 --- a/Sources/devctl/CLI.swift +++ b/Sources/devctl/CLI.swift @@ -613,7 +613,7 @@ struct HookCommand: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "hook", abstract: "Agent-harness session hooks.", - subcommands: [HookInstall.self, HookClaudeSessionStart.self] + subcommands: [HookInstall.self, HookClaudeSessionStart.self, HookCursorSessionStart.self] ) } @@ -624,7 +624,7 @@ struct HookInstall: AsyncParsableCommand { @OptionGroup var global: GlobalOptions - @Option(help: "Harness to install for: \(harnessAdapters.map(\.name).joined(separator: ", ")).") + @Option(help: "Harness to install for: \(harnessAdapters.map(\.name).joined(separator: ", ")) (default: claude).") var harness: String = "claude" @Flag(help: "Also print the statusline wiring suggestion.") @@ -639,8 +639,7 @@ struct HookInstall: AsyncParsableCommand { message: "unknown harness '\(harness)'"), json: global.json) } - let devctlPath = URL(fileURLWithPath: CommandLine.arguments[0]) - .resolvingSymlinksInPath().path + let devctlPath = CLISelf.path do { let summary = try adapter.install(devctlPath: devctlPath) var output = summary @@ -665,11 +664,7 @@ struct HookClaudeSessionStart: AsyncParsableCommand { func run() async throws { let stdin = FileHandle.standardInput.readDataToEndOfFile() - var cwd = FileManager.default.currentDirectoryPath - if let payload = try? JSONSerialization.jsonObject(with: stdin) as? [String: Any], - let hookCwd = payload["cwd"] as? String { - cwd = hookCwd - } + let cwd = HookSessionCwd.resolve(stdin: stdin) /** Project resolution without --project: reuse the CLI's walk from the hook cwd by chdir-ing there first. */ FileManager.default.changeCurrentDirectoryPath(cwd) @@ -687,6 +682,25 @@ struct HookClaudeSessionStart: AsyncParsableCommand { } } +/** Invoked by Cursor's sessionStart hook. Emits {additional_context} (Cursor's + snake_case schema). Same silence / exit-0 guarantees as the Claude hook. */ +struct HookCursorSessionStart: AsyncParsableCommand { + static let configuration = CommandConfiguration( + commandName: "cursor-session-start", shouldDisplay: false) + + func run() async throws { + let stdin = FileHandle.standardInput.readDataToEndOfFile() + let cwd = HookSessionCwd.resolve(stdin: stdin) + FileManager.default.changeCurrentDirectoryPath(cwd) + let project = GlobalOptions.resolveProject(from: cwd) + guard let text = await AgentContext.render(project: project) else { return } + let output: [String: Any] = ["additional_context": text] + if let data = try? JSONSerialization.data(withJSONObject: output) { + FileHandle.standardOutput.write(data) + } + } +} + /** Statusline helper: reads the harness's statusline stdin JSON, prints one compact presence line for the cwd's project (empty when nothing to show). */ struct Statusline: AsyncParsableCommand { diff --git a/Sources/devctl/HookSupport.swift b/Sources/devctl/HookSupport.swift index b216220..d886b76 100644 --- a/Sources/devctl/HookSupport.swift +++ b/Sources/devctl/HookSupport.swift @@ -1,6 +1,26 @@ import DevCtlKit +import Darwin import Foundation +/** Absolute path of this running `devctl` binary. Prefer over + CommandLine.arguments[0], which is often a bare PATH name and would resolve + relative to cwd (breaking `hook install` when invoked as `devctl` from a + project directory). */ +enum CLISelf { + static var path: String { + var size = UInt32(PATH_MAX) + var buf = [CChar](repeating: 0, count: Int(size)) + if _NSGetExecutablePath(&buf, &size) != 0 { + buf = [CChar](repeating: 0, count: Int(size)) + _ = _NSGetExecutablePath(&buf, &size) + } + let end = buf.firstIndex(of: 0) ?? buf.endIndex + let bytes = buf[.. String { + if let payload = try? JSONSerialization.jsonObject(with: stdin) as? [String: Any] { + if let roots = payload["workspace_roots"] as? [String], let first = roots.first, + !first.isEmpty + { + return first + } + if let cwd = payload["cwd"] as? String, !cwd.isEmpty { return cwd } + } + if let env = ProcessInfo.processInfo.environment["CURSOR_PROJECT_DIR"], !env.isEmpty { + return env + } + return FileManager.default.currentDirectoryPath + } +} /** Claude Code: SessionStart hook with the compact matcher (fires right after compaction, exactly when agents forget), merged into user settings without @@ -79,10 +119,15 @@ struct ClaudeCodeAdapter: HarnessAdapter { } var hooks = settings["hooks"] as? [String: Any] ?? [:] var sessionStart = hooks["SessionStart"] as? [[String: Any]] ?? [] + if let repaired = repairClaudeSessionStart(sessionStart: &sessionStart, command: command) { + hooks["SessionStart"] = sessionStart + settings["hooks"] = hooks + try writeSettings(settings) + return repaired + } let alreadyInstalled = sessionStart.contains { entry in ((entry["hooks"] as? [[String: Any]]) ?? []).contains { hook in - (hook["command"] as? String)?.contains("devctl hook claude-session-start") == true - || (hook["command"] as? String) == command + (hook["command"] as? String) == command } } if alreadyInstalled { @@ -94,11 +139,100 @@ struct ClaudeCodeAdapter: HarnessAdapter { ]) hooks["SessionStart"] = sessionStart settings["hooks"] = hooks + try writeSettings(settings) + return "Claude Code SessionStart hook installed (matcher startup|resume|clear|compact) in \(settingsURL.path)" + } + + private func writeSettings(_ settings: [String: Any]) throws { let data = try JSONSerialization.data( withJSONObject: settings, options: [.prettyPrinted, .sortedKeys]) try FileManager.default.createDirectory( at: settingsURL.deletingLastPathComponent(), withIntermediateDirectories: true) try data.write(to: settingsURL) - return "Claude Code SessionStart hook installed (matcher startup|resume|clear|compact) in \(settingsURL.path)" + } + + /** Rewrite a prior install whose command path no longer resolves (e.g. a + bare `devctl` that was resolved relative to cwd at install time). */ + private func repairClaudeSessionStart(sessionStart: inout [[String: Any]], command: String) + -> String? + { + var changed = false + for i in sessionStart.indices { + guard var entryHooks = sessionStart[i]["hooks"] as? [[String: Any]] else { continue } + for j in entryHooks.indices { + guard let existing = entryHooks[j]["command"] as? String, + existing.contains("devctl hook claude-session-start"), + existing != command + else { continue } + entryHooks[j]["command"] = command + changed = true + } + if changed { sessionStart[i]["hooks"] = entryHooks } + } + return changed + ? "Claude Code SessionStart hook path repaired in \(settingsURL.path)" : nil + } +} + +/** Cursor: sessionStart hook merged into ~/.cursor/hooks.json without clobbering + existing entries. Emits {additional_context} (snake_case; Cursor's schema). */ +struct CursorAdapter: HarnessAdapter { + let name = "cursor" + + var settingsURL: URL { + FileManager.default.homeDirectoryForCurrentUser.appending(path: ".cursor/hooks.json") + } + + func install(devctlPath: String) throws -> String { + let command = "\(devctlPath) hook cursor-session-start" + var settings: [String: Any] = ["version": 1] + if let data = try? Data(contentsOf: settingsURL), + let parsed = try? JSONSerialization.jsonObject(with: data) as? [String: Any] + { + settings = parsed + if settings["version"] == nil { settings["version"] = 1 } + } + var hooks = settings["hooks"] as? [String: Any] ?? [:] + var sessionStart = hooks["sessionStart"] as? [[String: Any]] ?? [] + if let repaired = repairCursorSessionStart(sessionStart: &sessionStart, command: command) { + hooks["sessionStart"] = sessionStart + settings["hooks"] = hooks + try writeSettings(settings) + return repaired + } + let alreadyInstalled = sessionStart.contains { entry in + (entry["command"] as? String) == command + } + if alreadyInstalled { + return "Cursor sessionStart hook already installed (\(settingsURL.path))" + } + sessionStart.append(["command": command]) + hooks["sessionStart"] = sessionStart + settings["hooks"] = hooks + try writeSettings(settings) + return "Cursor sessionStart hook installed in \(settingsURL.path)" + } + + private func writeSettings(_ settings: [String: Any]) throws { + let data = try JSONSerialization.data( + withJSONObject: settings, options: [.prettyPrinted, .sortedKeys]) + try FileManager.default.createDirectory( + at: settingsURL.deletingLastPathComponent(), withIntermediateDirectories: true) + try data.write(to: settingsURL) + } + + private func repairCursorSessionStart(sessionStart: inout [[String: Any]], command: String) + -> String? + { + var changed = false + for i in sessionStart.indices { + guard let existing = sessionStart[i]["command"] as? String, + existing.contains("devctl hook cursor-session-start"), + existing != command + else { continue } + sessionStart[i]["command"] = command + changed = true + } + return changed ? "Cursor sessionStart hook path repaired in \(settingsURL.path)" : nil } } diff --git a/Sources/devctl/LaunchdAdmin.swift b/Sources/devctl/LaunchdAdmin.swift index e2c8976..8bac502 100644 --- a/Sources/devctl/LaunchdAdmin.swift +++ b/Sources/devctl/LaunchdAdmin.swift @@ -14,7 +14,7 @@ enum LaunchdAdmin { /** The devctld binary that ships alongside this devctl binary. */ static func bundledDaemonBinary() -> URL? { - let selfPath = URL(fileURLWithPath: CommandLine.arguments[0]).resolvingSymlinksInPath() + let selfPath = URL(fileURLWithPath: CLISelf.path) let sibling = selfPath.deletingLastPathComponent().appending(path: "devctld") return FileManager.default.isExecutableFile(atPath: sibling.path) ? sibling : nil } diff --git a/docs/cli-contract.md b/docs/cli-contract.md index 5221fe0..08f9017 100644 --- a/docs/cli-contract.md +++ b/docs/cli-contract.md @@ -57,5 +57,5 @@ Filled in per phase as each lands; golden tests reference the examples in this f - `devctl lock -- [--acquire-timeout 300] [--timeout 120]` → runs the command holding a project resource exclusively. Servers declaring the resource in their `locks` (devservers.json) are stopped first and re-ensured after (even on command failure); `ensure`/`start` of a declaring server is refused (`resource-locked`, naming the holder pid) while a live holder owns it; a dead holder auto-releases. Exit status is the command's. The mechanism for test harnesses whose private servers share mutable state (a local database) with a managed server. - `devctl daemon install|uninstall [--purge]|start|stop|restart|status`: launchd lifecycle. `stop` drains and writes a deliberate-stop marker that auto-bootstrap honors; `restart` drains, kickstarts, and re-ensures what ran; `install` stages-and-renames the daemon binary and captures the login-shell PATH into the agent plist. Reboot recovery: the LaunchAgent runs at load, and starting a server records a resume-on-boot intent; a machine shutdown drains without clearing it, so the next login's daemon brings back every server that was up. A deliberate `devctl stop`/`down` clears the intent, so only those stay down. - `devctl context`: the harness-agnostic session context: a fenced `` plain-text block (server phases, URLs, log paths, the ensure/wait/why/logs cheat-sheet) for the cwd's project. Silent (exit 0) when the project is unregistered or untrusted or the daemon is down; never bootstraps; never contains raw log lines or command strings. -- `devctl hook install [--harness claude] [--statusline]`: idempotently wires a session-start hook into the harness's settings (claude: SessionStart with matcher `startup|resume|clear|compact` in ~/.claude/settings.json, emitting `hookSpecificOutput.additionalContext`). Adding a harness: CONTRIBUTING.md. +- `devctl hook install [--harness claude|cursor] [--statusline]`: idempotently wires a session-start hook into the harness's settings (claude: SessionStart with matcher `startup|resume|clear|compact` in ~/.claude/settings.json, emitting `hookSpecificOutput.additionalContext`; cursor: sessionStart in ~/.cursor/hooks.json, emitting `{additional_context}`). Adding a harness: CONTRIBUTING.md. - `devctl statusline`: reads harness statusline stdin JSON (workspace.current_dir or cwd), prints `web:3000 ok · api crashed` for the project, empty otherwise. From 28bfebaa9256a27733ef0b014e1d9ffc81a36745 Mon Sep 17 00:00:00 2001 From: Evan Jacobs Date: Fri, 24 Jul 2026 11:28:43 -0400 Subject: [PATCH 2/7] Add minimal macos-26 CI for build and unit tests Standard arm64 runner only: swift build + swift test, SPM cache, concurrency cancel, and docs path-ignore. Skips smoke and large runners to keep wall time (and any future private-repo minutes) low. --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 2 +- 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5ddf584 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,46 @@ +# Minimal macOS gate: compile every product and run unit tests. +# Intentionally skips scripts/smoke.sh and smoke-launchd.sh (longer, and +# launchd mutates the runner). Standard macos-26 arm64 only: no large / +# xlarge / intel labels (those bill more for the same work). +name: CI + +on: + push: + branches: [main] + paths-ignore: + - '**.md' + - 'docs/**' + pull_request: + paths-ignore: + - '**.md' + - 'docs/**' + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build: + runs-on: macos-26 + timeout-minutes: 12 + steps: + - uses: actions/checkout@v4 + + - name: Cache SwiftPM + uses: actions/cache@v4 + with: + path: | + .build + ~/Library/Caches/org.swift.swiftpm + key: spm-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('Package.resolved') }} + restore-keys: | + spm-${{ runner.os }}-${{ runner.arch }}- + + - name: Build + run: swift build + + - name: Test + run: swift test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 596b658..6d162ab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to devctl -devctl is a personal tool first; issues and patches are welcome all the same. Read `CLAUDE.md` (or its `AGENTS.md` symlink) for the codebase map, invariants, and commands, and `docs/cli-contract.md` for the JSON surface. `make test` and `scripts/smoke.sh` must pass; `scripts/smoke-launchd.sh` exercises the real launchd lifecycle if your change touches daemon management. +devctl is a personal tool first; issues and patches are welcome all the same. Read `CLAUDE.md` (or its `AGENTS.md` symlink) for the codebase map, invariants, and commands, and `docs/cli-contract.md` for the JSON surface. `make test` and `scripts/smoke.sh` must pass locally; `scripts/smoke-launchd.sh` exercises the real launchd lifecycle if your change touches daemon management. GitHub Actions (`.github/workflows/ci.yml`) runs `swift build` + `swift test` on `macos-26` only: no smoke, no large runners. ## Adding an agent-harness adapter From d45a2cd2a41f7d5da594bc9eab547dc913a6f18a Mon Sep 17 00:00:00 2001 From: Evan Jacobs Date: Fri, 24 Jul 2026 11:35:01 -0400 Subject: [PATCH 3/7] Fix flaky unhealthyAfterThresholdAndRecovery race The script recovered on the next probe after the third failure, so the unhealthy window was one interval (~30ms) and pollPhase's 50ms sample often missed it on CI. Pad failures so the assertion can observe the phase. --- Tests/DevCtlDaemonCoreTests/HealthTests.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Tests/DevCtlDaemonCoreTests/HealthTests.swift b/Tests/DevCtlDaemonCoreTests/HealthTests.swift index 67a381e..088196d 100644 --- a/Tests/DevCtlDaemonCoreTests/HealthTests.swift +++ b/Tests/DevCtlDaemonCoreTests/HealthTests.swift @@ -71,10 +71,14 @@ private func pollPhase( } @Test func unhealthyAfterThresholdAndRecovery() async throws { + /** Pad failures past unhealthyAfter so the unhealthy phase lasts longer + than pollPhase's 50ms sample: a tight [true,F,F,F,true] script can + recover before the assertion sees .unhealthy (flake on CI). */ let (supervisor, _, _) = try makeSupervisor( command: ["/bin/sh", "-c", "sleep 30"], healthcheck: fastTCP, - prober: ScriptedProber(script: ProbeScript([true, false, false, false, true]))) + prober: ScriptedProber( + script: ProbeScript([true] + Array(repeating: false, count: 12) + [true]))) _ = await supervisor.start() #expect(await pollPhase(supervisor, until: .running) == .running) #expect(await pollPhase(supervisor, until: .unhealthy) == .unhealthy) From 2ac148addf1c580b88b214d5b0dde67025b2e543 Mon Sep 17 00:00:00 2001 From: Evan Jacobs Date: Fri, 24 Jul 2026 11:38:01 -0400 Subject: [PATCH 4/7] Harden CLISelf.path against _NSGetExecutablePath failure Use the standard size-query then fill pattern, and fall back to an absolute arg0 or PATH lookup instead of writing an empty path into hook settings. --- Sources/devctl/HookSupport.swift | 36 ++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/Sources/devctl/HookSupport.swift b/Sources/devctl/HookSupport.swift index d886b76..0d8418a 100644 --- a/Sources/devctl/HookSupport.swift +++ b/Sources/devctl/HookSupport.swift @@ -8,16 +8,40 @@ import Foundation project directory). */ enum CLISelf { static var path: String { - var size = UInt32(PATH_MAX) - var buf = [CChar](repeating: 0, count: Int(size)) - if _NSGetExecutablePath(&buf, &size) != 0 { - buf = [CChar](repeating: 0, count: Int(size)) - _ = _NSGetExecutablePath(&buf, &size) + if let raw = dyldExecutablePath() { + return URL(fileURLWithPath: raw).resolvingSymlinksInPath().path } + return fallbackPath() + } + + /** Standard 2-call `_NSGetExecutablePath`: query size, then fill. */ + private static func dyldExecutablePath() -> String? { + var size: UInt32 = 0 + _ = _NSGetExecutablePath(nil, &size) + guard size > 1 else { return nil } + var buf = [CChar](repeating: 0, count: Int(size)) + guard _NSGetExecutablePath(&buf, &size) == 0 else { return nil } let end = buf.firstIndex(of: 0) ?? buf.endIndex let bytes = buf[.. String { + let arg0 = CommandLine.arguments[0] + if arg0.hasPrefix("/") { + return URL(fileURLWithPath: arg0).resolvingSymlinksInPath().path + } + let name = URL(fileURLWithPath: arg0).lastPathComponent + let pathEnv = ProcessInfo.processInfo.environment["PATH"] ?? "" + for dir in pathEnv.split(separator: ":") where !dir.isEmpty { + let candidate = URL(fileURLWithPath: String(dir)).appending(path: name) + if FileManager.default.isExecutableFile(atPath: candidate.path) { + return candidate.resolvingSymlinksInPath().path + } + } + return URL(fileURLWithPath: arg0).resolvingSymlinksInPath().path } } From 5604ba2181a8a33b36e2eb9c17b5f09ff795960f Mon Sep 17 00:00:00 2001 From: Evan Jacobs Date: Fri, 24 Jul 2026 11:41:56 -0400 Subject: [PATCH 5/7] Add Changesets for GitHub-only vX.Y.Z releases Private root package.json tracks the product version (currently 1.1.0, matching existing tags). Release workflow on ubuntu opens the Version Packages PR and, on merge, tags and creates the GitHub release without npm publish. Sync DevCtlVersion.version from package.json on bump. --- .changeset/README.md | 11 + .changeset/config.json | 19 + .changeset/cursor-session-hook.md | 5 + .github/workflows/release.yml | 43 + .gitignore | 1 + CHANGELOG.md | 17 + CLAUDE.md | 2 +- CONTRIBUTING.md | 4 + Sources/DevCtlKit/Model/Models.swift | 2 +- package-lock.json | 1365 ++++++++++++++++++++++++++ package.json | 20 + scripts/sync-version.mjs | 31 + 12 files changed, 1518 insertions(+), 2 deletions(-) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .changeset/cursor-session-hook.md create mode 100644 .github/workflows/release.yml create mode 100644 CHANGELOG.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/sync-version.mjs diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..991e760 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,11 @@ +# Changesets + +Version and changelog for GitHub releases. This repo is a Swift product: the root `package.json` is private and exists only so Changesets can bump `vX.Y.Z` tags and write `CHANGELOG.md`. Nothing is published to npm. + +## Workflow + +1. With a user-facing change, run `npm run changeset` (or `npx changeset`) and pick patch / minor / major. +2. Merge the PR. On `main`, the Release workflow opens or updates a **Version Packages** PR. +3. Merge that PR when ready. The workflow tags `vX.Y.Z` (matching historical releases) and creates the GitHub release from the changelog. + +`npm run version` also syncs `Sources/DevCtlKit/Model/Models.swift` (`DevCtlVersion.version`) from `package.json` so `devctl --version` and the app bundle stay aligned. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..6109106 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": [ + "@changesets/changelog-github", + { "repo": "quantizor/devctl" } + ], + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [], + "prettier": false, + "privatePackages": { + "version": true, + "tag": true + } +} diff --git a/.changeset/cursor-session-hook.md b/.changeset/cursor-session-hook.md new file mode 100644 index 0000000..7d89650 --- /dev/null +++ b/.changeset/cursor-session-hook.md @@ -0,0 +1,5 @@ +--- +"devctl": minor +--- + +Add Cursor sessionStart harness (`devctl hook install --harness cursor`) so Agent sessions get live `` context, plus minimal macos-26 CI for build and unit tests. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..844cd14 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,43 @@ +# GitHub-only releases via Changesets (ubuntu; no macOS minutes). +# Creates/updates the Version Packages PR; on merge, tags vX.Y.Z and +# opens a GitHub release from CHANGELOG.md. Does not publish to npm. +name: Release + +on: + push: + branches: [main] + workflow_dispatch: + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: write + pull-requests: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Create Release Pull Request or GitHub Release + id: changesets + uses: changesets/action@v1 + with: + title: Version Packages + commit: Version Packages + version: npm run version + publish: npm run release + createGithubReleases: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 08d9f18..922b61c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +node_modules/ .build/ .swiftpm/ devctl.app/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c6ba1d5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +All notable changes to this project are documented here. Releases are tagged +`vX.Y.Z` on GitHub (same scheme Changesets uses for this root package). + +## 1.1.0 + +### Patch Changes + +- Fixed a health check issue with some dev servers that primarily communicate over IPv6. +- Many design improvements. +- Keyboard navigation. +- Sorting & Filtering. + +## 1.0.0 + +Initial public release. diff --git a/CLAUDE.md b/CLAUDE.md index 5c8daf7..b2c63dd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -45,7 +45,7 @@ Engineering rules - Fields in type declarations, initializers, and literals are alphabetized; keep a non-alphabetical order only where the sequence is load-bearing and say why in a comment. Persisted names (JSON keys, file names) are plain English a non-engineer would read. - Error messages assume the reader knows nothing of internals: what happened, where, and the exact fix; wire errors carry a stable code and a hint that is the literal command to run. - Prose (docs, commits, PRs): American English, no em-dashes (use a colon, comma, or period), plain language over jargon, no attribution footers anywhere. PR and changeset descriptions lead with user-facing impact, not mechanism. -- Git: never git stash; use temp commits. The maintainer handles version bumps and publishing. A dependency change updates Package.resolved in the same commit and is verified by a build. +- Git: never git stash; use temp commits. Product version lives in package.json (Changesets); `npm run version` syncs `DevCtlVersion.version` in Models.swift. A dependency change updates Package.resolved in the same commit and is verified by a build. GitHub releases are tagged `vX.Y.Z` by the Release workflow; agents never bump versions or publish. - Docs carry no measured drift-prone numbers (timings, counts, coverage); state the budget and the command that prints the live figure. Docs track current state, never completions or history. BACKLOG.md is the only backlog, holds open work only, and the change that resolves an entry removes it. - Performance: hot paths (spool tailer, log query) get temporary microbenchmarks before an approach is chosen; a governor (throttle, cap) is never the fix for a cost problem, make the work itself cheaper. - No database, no migrations; on-disk compatibility is the defensive-load rule above. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d162ab..9b9e607 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,6 +2,10 @@ devctl is a personal tool first; issues and patches are welcome all the same. Read `CLAUDE.md` (or its `AGENTS.md` symlink) for the codebase map, invariants, and commands, and `docs/cli-contract.md` for the JSON surface. `make test` and `scripts/smoke.sh` must pass locally; `scripts/smoke-launchd.sh` exercises the real launchd lifecycle if your change touches daemon management. GitHub Actions (`.github/workflows/ci.yml`) runs `swift build` + `swift test` on `macos-26` only: no smoke, no large runners. +## Releases + +GitHub releases are driven by [Changesets](https://github.com/changesets/changesets) (see `.changeset/README.md`). With a user-facing change, run `npm run changeset`, commit the generated file, and merge. A Version Packages PR appears on `main`; merging it tags `vX.Y.Z` and opens the GitHub release. Nothing is published to npm: the root `package.json` is private and only tracks the product version. + ## Adding an agent-harness adapter The session-context payload is harness-agnostic: `devctl context` prints a fenced plain-text block describing the current project's servers, and `devctl statusline` prints a one-line presence summary from statusline stdin JSON. Wiring those into a harness is the only per-harness work. diff --git a/Sources/DevCtlKit/Model/Models.swift b/Sources/DevCtlKit/Model/Models.swift index 80fb255..1498497 100644 --- a/Sources/DevCtlKit/Model/Models.swift +++ b/Sources/DevCtlKit/Model/Models.swift @@ -4,7 +4,7 @@ import Foundation public enum DevCtlVersion { /** Wire protocol major version; clients abort on mismatch with `version-mismatch`. */ public static let proto = 1 - public static let version = "0.1.0" + public static let version = "1.1.0" } /** Lifecycle phase of a supervised server. `failed` means the spawn itself never diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..24f485b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1365 @@ +{ + "name": "devctl", + "version": "1.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "devctl", + "version": "1.1.0", + "license": "MIT", + "devDependencies": { + "@changesets/changelog-github": "^0.7.0", + "@changesets/cli": "^2.31.1" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@changesets/apply-release-plan": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.1.1.tgz", + "integrity": "sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/config": "^3.1.4", + "@changesets/get-version-range-type": "^0.4.0", + "@changesets/git": "^3.0.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "detect-indent": "^6.0.0", + "fs-extra": "^7.0.1", + "lodash.startcase": "^4.4.0", + "outdent": "^0.5.0", + "prettier": "^2.7.1", + "resolve-from": "^5.0.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/assemble-release-plan": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.10.tgz", + "integrity": "sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/changelog-git": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-0.2.1.tgz", + "integrity": "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0" + } + }, + "node_modules/@changesets/changelog-github": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-0.7.0.tgz", + "integrity": "sha512-rBsbRvc4TVn+FvFnOVM3LxlFJfTXXCp8gfVJ+0BubxWNSVnLuAzowi5j+IEraLLP52w8AAs9QfKbPS3MMiXQJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/get-github-info": "^0.8.0", + "@changesets/types": "^6.1.0", + "dotenv": "^8.1.0" + } + }, + "node_modules/@changesets/cli": { + "version": "2.31.1", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-2.31.1.tgz", + "integrity": "sha512-uO05WTcRBwuVOJVSW8Cmpqw6q0WDL53ajGCMyszutvOe5toOnunbpM4jZzf+qxBOz7i0AzopZ8diBuewjmF40w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/apply-release-plan": "^7.1.1", + "@changesets/assemble-release-plan": "^6.0.10", + "@changesets/changelog-git": "^0.2.1", + "@changesets/config": "^3.1.4", + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/get-release-plan": "^4.0.16", + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.7", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@changesets/write": "^0.4.0", + "@inquirer/external-editor": "^1.0.2", + "@manypkg/get-packages": "^1.1.3", + "ansi-colors": "^4.1.3", + "enquirer": "^2.4.1", + "fs-extra": "^7.0.1", + "mri": "^1.2.0", + "package-manager-detector": "^0.2.0", + "picocolors": "^1.1.0", + "resolve-from": "^5.0.0", + "semver": "^7.5.3", + "spawndamnit": "^3.0.1", + "term-size": "^2.1.0" + }, + "bin": { + "changeset": "bin.js" + } + }, + "node_modules/@changesets/config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-3.1.4.tgz", + "integrity": "sha512-pf0bvD/v6WI2cRlZ6hzpjtZdSlXDXMAJ+Iz7xfFzV4ZxJ8OGGAON+1qYc99ZPrijnt4xp3VGG7eNvAOGS24V1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/get-dependents-graph": "^2.1.4", + "@changesets/logger": "^0.1.1", + "@changesets/should-skip-package": "^0.1.2", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1", + "micromatch": "^4.0.8" + } + }, + "node_modules/@changesets/errors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-0.2.0.tgz", + "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", + "dev": true, + "license": "MIT", + "dependencies": { + "extendable-error": "^0.1.5" + } + }, + "node_modules/@changesets/get-dependents-graph": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-2.1.4.tgz", + "integrity": "sha512-ZsS00x6WvmHq3sQv8oCMwL0f/z3wbXCVuSVTJwCnnmbC/iBdNJGFx1EcbMG4PC6sXRyH69liM4A2WKXzn/kRPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "picocolors": "^1.1.0", + "semver": "^7.5.3" + } + }, + "node_modules/@changesets/get-github-info": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-0.8.0.tgz", + "integrity": "sha512-cRnC+xdF0JIik7coko3iUP9qbnfi1iJQ3sAa6dE+Tx3+ET8bjFEm63PA4WEohgjYcmsOikPHWzPsMWWiZmntOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dataloader": "^1.4.0", + "node-fetch": "^2.5.0" + } + }, + "node_modules/@changesets/get-release-plan": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/@changesets/get-release-plan/-/get-release-plan-4.0.16.tgz", + "integrity": "sha512-2K5Om6CrMPm45rtvckfzWo7e9jOVCKLCnXia5eUPaURH7/LWzri7pK1TycdzAuAtehLkW7VPbWLCSExTHmiI6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/assemble-release-plan": "^6.0.10", + "@changesets/config": "^3.1.4", + "@changesets/pre": "^2.0.2", + "@changesets/read": "^0.6.7", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/get-version-range-type": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz", + "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/git": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-3.0.4.tgz", + "integrity": "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@manypkg/get-packages": "^1.1.3", + "is-subdir": "^1.1.1", + "micromatch": "^4.0.8", + "spawndamnit": "^3.0.1" + } + }, + "node_modules/@changesets/logger": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@changesets/logger/-/logger-0.1.1.tgz", + "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/parse": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-0.4.3.tgz", + "integrity": "sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "js-yaml": "^4.1.1" + } + }, + "node_modules/@changesets/pre": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-2.0.2.tgz", + "integrity": "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^0.2.0", + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3", + "fs-extra": "^7.0.1" + } + }, + "node_modules/@changesets/read": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-0.6.7.tgz", + "integrity": "sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/git": "^3.0.4", + "@changesets/logger": "^0.1.1", + "@changesets/parse": "^0.4.3", + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "p-filter": "^2.1.0", + "picocolors": "^1.1.0" + } + }, + "node_modules/@changesets/should-skip-package": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-0.1.2.tgz", + "integrity": "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "@manypkg/get-packages": "^1.1.3" + } + }, + "node_modules/@changesets/types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-6.1.0.tgz", + "integrity": "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@changesets/write": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-0.4.0.tgz", + "integrity": "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^6.1.0", + "fs-extra": "^7.0.1", + "human-id": "^4.1.1", + "prettier": "^2.7.1" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@manypkg/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@types/node": "^12.7.1", + "find-up": "^4.1.0", + "fs-extra": "^8.1.0" + } + }, + "node_modules/@manypkg/find-root/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@manypkg/get-packages": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-1.1.3.tgz", + "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5", + "@changesets/types": "^4.0.1", + "@manypkg/find-root": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "^11.0.0", + "read-yaml-file": "^1.1.0" + } + }, + "node_modules/@manypkg/get-packages/node_modules/@changesets/types": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-4.1.0.tgz", + "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@manypkg/get-packages/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/better-path-resolve": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz", + "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-windows": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dataloader": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz", + "integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extendable-error": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz", + "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/human-id": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.2.0.tgz", + "integrity": "sha512-K3GbkIWqyvvlpfhBPlbEvD97TtqBpAYA4kt+cn2lD2x2HuohzZCibcA2nOlnJT6exqvJLggoB5nv2dNf192nEA==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-subdir": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz", + "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "better-path-resolve": "1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/outdent": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz", + "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-map": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-manager-detector": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", + "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^0.2.7" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/read-yaml-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-yaml-file/-/read-yaml-file-1.1.0.tgz", + "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.5", + "js-yaml": "^3.6.1", + "pify": "^4.0.1", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/read-yaml-file/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/read-yaml-file/node_modules/js-yaml": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/spawndamnit": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz", + "integrity": "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "cross-spawn": "^7.0.5", + "signal-exit": "^4.0.1" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..000c769 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "devctl", + "version": "1.1.0", + "private": true, + "description": "Version and changelog source of truth for GitHub releases (Swift product; not published to npm).", + "repository": { + "type": "git", + "url": "git+https://github.com/quantizor/devctl.git" + }, + "license": "MIT", + "scripts": { + "changeset": "changeset", + "version": "changeset version && node scripts/sync-version.mjs", + "release": "changeset publish" + }, + "devDependencies": { + "@changesets/changelog-github": "^0.7.0", + "@changesets/cli": "^2.31.1" + } +} diff --git a/scripts/sync-version.mjs b/scripts/sync-version.mjs new file mode 100644 index 0000000..c5080f6 --- /dev/null +++ b/scripts/sync-version.mjs @@ -0,0 +1,31 @@ +#!/usr/bin/env node +/** Keep DevCtlVersion.version in lockstep with package.json after + `changeset version`. GitHub releases use package.json; the CLI/app + report the Swift constant. */ +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const version = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version; +if (typeof version !== "string" || !/^\d+\.\d+\.\d+/.test(version)) { + console.error(`sync-version: refusing unexpected package.json version: ${version}`); + process.exit(1); +} + +const swiftPath = join(root, "Sources/DevCtlKit/Model/Models.swift"); +const before = readFileSync(swiftPath, "utf8"); +const after = before.replace( + /public static let version = "[^"]+"/, + `public static let version = "${version}"`, +); +if (after === before) { + if (!before.includes(`public static let version = "${version}"`)) { + console.error("sync-version: DevCtlVersion.version declaration not found in Models.swift"); + process.exit(1); + } + console.log(`sync-version: already at ${version}`); + process.exit(0); +} +writeFileSync(swiftPath, after); +console.log(`sync-version: Models.swift -> ${version}`); From 7ff0a239d86ed2d854436ed5003c2413ca58a0f0 Mon Sep 17 00:00:00 2001 From: Evan Jacobs Date: Fri, 24 Jul 2026 11:46:08 -0400 Subject: [PATCH 6/7] Omit CI from the Cursor harness changeset note Internal-only work never deserves a changeset; document that in the release contributor notes. --- .changeset/README.md | 2 +- .changeset/cursor-session-hook.md | 2 +- CONTRIBUTING.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/README.md b/.changeset/README.md index 991e760..c6ef363 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -4,7 +4,7 @@ Version and changelog for GitHub releases. This repo is a Swift product: the roo ## Workflow -1. With a user-facing change, run `npm run changeset` (or `npx changeset`) and pick patch / minor / major. +1. With a user-facing change, run `npm run changeset` (or `npx changeset`) and pick patch / minor / major. Internal-only work (CI, docs for agents, refactor with no behavior change) never gets a changeset. 2. Merge the PR. On `main`, the Release workflow opens or updates a **Version Packages** PR. 3. Merge that PR when ready. The workflow tags `vX.Y.Z` (matching historical releases) and creates the GitHub release from the changelog. diff --git a/.changeset/cursor-session-hook.md b/.changeset/cursor-session-hook.md index 7d89650..ce7a6e1 100644 --- a/.changeset/cursor-session-hook.md +++ b/.changeset/cursor-session-hook.md @@ -2,4 +2,4 @@ "devctl": minor --- -Add Cursor sessionStart harness (`devctl hook install --harness cursor`) so Agent sessions get live `` context, plus minimal macos-26 CI for build and unit tests. +Add Cursor sessionStart harness (`devctl hook install --harness cursor`) so Agent sessions get live `` context. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b9e607..4009613 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ devctl is a personal tool first; issues and patches are welcome all the same. Re ## Releases -GitHub releases are driven by [Changesets](https://github.com/changesets/changesets) (see `.changeset/README.md`). With a user-facing change, run `npm run changeset`, commit the generated file, and merge. A Version Packages PR appears on `main`; merging it tags `vX.Y.Z` and opens the GitHub release. Nothing is published to npm: the root `package.json` is private and only tracks the product version. +GitHub releases are driven by [Changesets](https://github.com/changesets/changesets) (see `.changeset/README.md`). With a user-facing change, run `npm run changeset`, commit the generated file, and merge. Internal-only work (CI, agent docs, no-behavior refactors) never gets a changeset. A Version Packages PR appears on `main`; merging it tags `vX.Y.Z` and opens the GitHub release. Nothing is published to npm: the root `package.json` is private and only tracks the product version. ## Adding an agent-harness adapter From 31a8dc80c520c81c616f0e3dd73204f840fb79a2 Mon Sep 17 00:00:00 2001 From: Evan Jacobs Date: Fri, 24 Jul 2026 11:47:29 -0400 Subject: [PATCH 7/7] Point AGENTS.md at CONTRIBUTING for commit and changeset hygiene --- CLAUDE.md | 4 ++-- CONTRIBUTING.md | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b2c63dd..a7bfc3c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # devctl -macOS menu bar command center for local dev servers, built so coding agents never lose track of running servers across compaction and session boundaries. All Swift, no Xcode. Design spec and phase plan: docs/design.md (the map for everything not yet built). CLI JSON contract: docs/cli-contract.md. Open work: BACKLOG.md. +macOS menu bar command center for local dev servers, built so coding agents never lose track of running servers across compaction and session boundaries. All Swift, no Xcode. Design spec and phase plan: docs/design.md (the map for everything not yet built). CLI JSON contract: docs/cli-contract.md. Open work: BACKLOG.md. Commit and changeset hygiene: CONTRIBUTING.md. Identity and stack - Swift 6.3 toolchain, swift-tools-version 6.2, Swift 6 language mode with strict concurrency. macOS 14+, single SPM package. @@ -45,7 +45,7 @@ Engineering rules - Fields in type declarations, initializers, and literals are alphabetized; keep a non-alphabetical order only where the sequence is load-bearing and say why in a comment. Persisted names (JSON keys, file names) are plain English a non-engineer would read. - Error messages assume the reader knows nothing of internals: what happened, where, and the exact fix; wire errors carry a stable code and a hint that is the literal command to run. - Prose (docs, commits, PRs): American English, no em-dashes (use a colon, comma, or period), plain language over jargon, no attribution footers anywhere. PR and changeset descriptions lead with user-facing impact, not mechanism. -- Git: never git stash; use temp commits. Product version lives in package.json (Changesets); `npm run version` syncs `DevCtlVersion.version` in Models.swift. A dependency change updates Package.resolved in the same commit and is verified by a build. GitHub releases are tagged `vX.Y.Z` by the Release workflow; agents never bump versions or publish. +- Git: never git stash; use temp commits. A dependency change updates Package.resolved in the same commit and is verified by a build. Commit messages, Changesets (when to write one), and release tagging live in CONTRIBUTING.md; agents never bump versions or publish. - Docs carry no measured drift-prone numbers (timings, counts, coverage); state the budget and the command that prints the live figure. Docs track current state, never completions or history. BACKLOG.md is the only backlog, holds open work only, and the change that resolves an entry removes it. - Performance: hot paths (spool tailer, log query) get temporary microbenchmarks before an approach is chosen; a governor (throttle, cap) is never the fix for a cost problem, make the work itself cheaper. - No database, no migrations; on-disk compatibility is the defensive-load rule above. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4009613..4a97d6a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,16 @@ devctl is a personal tool first; issues and patches are welcome all the same. Read `CLAUDE.md` (or its `AGENTS.md` symlink) for the codebase map, invariants, and commands, and `docs/cli-contract.md` for the JSON surface. `make test` and `scripts/smoke.sh` must pass locally; `scripts/smoke-launchd.sh` exercises the real launchd lifecycle if your change touches daemon management. GitHub Actions (`.github/workflows/ci.yml`) runs `swift build` + `swift test` on `macos-26` only: no smoke, no large runners. +## Commit and changeset hygiene + +- Commits focus on why, not a file list; American English; no em-dashes; no attribution footers. +- Never `git stash` (use temp commits). Agents never bump versions or publish. +- User-facing changes get a Changeset (`npm run changeset`); see `.changeset/README.md`. Internal-only work (CI, agent docs, no-behavior refactors) never gets a changeset. +- Product version lives in `package.json`. `npm run version` (used by the Release workflow) syncs `DevCtlVersion.version` in `Sources/DevCtlKit/Model/Models.swift`. + ## Releases -GitHub releases are driven by [Changesets](https://github.com/changesets/changesets) (see `.changeset/README.md`). With a user-facing change, run `npm run changeset`, commit the generated file, and merge. Internal-only work (CI, agent docs, no-behavior refactors) never gets a changeset. A Version Packages PR appears on `main`; merging it tags `vX.Y.Z` and opens the GitHub release. Nothing is published to npm: the root `package.json` is private and only tracks the product version. +GitHub releases are driven by [Changesets](https://github.com/changesets/changesets). After changesets land on `main`, a Version Packages PR appears; merging it tags `vX.Y.Z` and opens the GitHub release. Nothing is published to npm: the root `package.json` is private and only tracks the product version. ## Adding an agent-harness adapter