Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .agents/pm/history/pm-github-yhhz.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{"ts":"2026-08-14T05:58:22.786Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2","agent_model_source":"environment","agent_instance":"56b7d1a35febb62ac00aa45f","agent_provenance":{"model":{"value":"glm-5.2","source":"environment"},"effort":null,"role":null,"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-github-yhhz"},{"op":"add","path":"/metadata/title","value":"Scope preflight override to pm-github's owned commands"},{"op":"add","path":"/metadata/description","value":"registerPreflight used the bare-function (global) form, which collides pairwise with every other installed package's preflight override (pm health reports extension_preflight_override_collision). Rewrite it to the scoped object form declaring exactly the mutating github/gh-issues command paths the override guards, so it no longer contends with other packages. The runtime matches a command against the commands array by exact normalized path, so the array lists the full command paths (github sync, github export, github import, gh-issues import, github project import, github project sync) — not a bare top-level name, which would never match and silently disable the early warning. The authoritative credential gate stays in the command handlers; this only narrows the override's scope."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":[]},{"op":"add","path":"/metadata/created_at","value":"2026-08-14T05:58:22.786Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-08-14T05:58:22.786Z"},{"op":"add","path":"/metadata/author","value":"pi-agent"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"735c0b5b215bb02c81b81865a6bbee96f50cf9c648a5704415da417f05bba0af","message":""}
{"ts":"2026-08-14T05:58:31.685Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2","agent_model_source":"environment","agent_instance":"56b7d1a35febb62ac00aa45f","agent_provenance":{"model":{"value":"glm-5.2","source":"environment"},"effort":null,"role":null,"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-14T05:58:31.685Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"735c0b5b215bb02c81b81865a6bbee96f50cf9c648a5704415da417f05bba0af","after_hash":"c7ce8ad4c59908200381f1c7cedf100be9d563e1ab3852bf922139006b4abc7b"}
{"ts":"2026-08-14T05:58:32.683Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2","agent_model_source":"environment","agent_instance":"56b7d1a35febb62ac00aa45f","agent_provenance":{"model":{"value":"glm-5.2","source":"environment"},"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-14T05:58:32.683Z"},{"op":"add","path":"/metadata/files","value":[{"path":"index.ts","scope":"project"}]}],"before_hash":"c7ce8ad4c59908200381f1c7cedf100be9d563e1ab3852bf922139006b4abc7b","after_hash":"3162edc6bd1a4cad452ed58c19dd83f9e7b364a234919626400c3e9f49ffe2fc"}
{"ts":"2026-08-14T05:58:33.826Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2","agent_model_source":"environment","agent_instance":"56b7d1a35febb62ac00aa45f","agent_provenance":{"model":{"value":"glm-5.2","source":"environment"},"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/1","value":{"path":"test/smoke.test.ts","scope":"project"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-14T05:58:33.826Z"}],"before_hash":"3162edc6bd1a4cad452ed58c19dd83f9e7b364a234919626400c3e9f49ffe2fc","after_hash":"4823285e0cd9ce89220d228228453a9908211b37e7dc0515182159dfd2526a92"}
{"ts":"2026-08-14T06:01:10.116Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2","agent_model_source":"environment","agent_instance":"56b7d1a35febb62ac00aa45f","agent_provenance":{"model":{"value":"glm-5.2","source":"environment"},"effort":null,"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-14T06:01:10.116Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"npm test","path":"test/smoke.test.ts","scope":"project","note":"Asserts registerPreflight is a scoped object whose commands array equals pm-github's owned mutating command paths (github sync, github export, github import, gh-issues import, github project import, github project sync) and run is a function. Test title: preflight override is scoped to pm-github's owned command paths"}]}],"before_hash":"4823285e0cd9ce89220d228228453a9908211b37e7dc0515182159dfd2526a92","after_hash":"a6a09803b8c2205d954cf15c3bde20e17da3a0d9fb2401c3ed9cc9b3613dcccf"}
{"ts":"2026-08-14T06:03:30.132Z","author":"pi-agent","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.2","agent_model_source":"environment","agent_instance":"56b7d1a35febb62ac00aa45f","agent_provenance":{"model":{"value":"glm-5.2","source":"environment"},"effort":null,"role":null,"topic":null},"op":"close","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-14T06:03:30.132Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-08-14T06:03:30.121Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-08-14T06:03:30.121Z"},{"op":"add","path":"/metadata/close_reason","value":"completed"}],"before_hash":"a6a09803b8c2205d954cf15c3bde20e17da3a0d9fb2401c3ed9cc9b3613dcccf","after_hash":"0434a8c16a01573390ab85d68f7bd1e1a7259095dd7bdfd6f841e9398fe7ba6c"}
19 changes: 19 additions & 0 deletions .agents/pm/issues/pm-github-yhhz.toon
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
id: pm-github-yhhz
title: Scope preflight override to pm-github's owned commands
description: "registerPreflight used the bare-function (global) form, which collides pairwise with every other installed package's preflight override (pm health reports extension_preflight_override_collision). Rewrite it to the scoped object form declaring exactly the mutating github/gh-issues command paths the override guards, so it no longer contends with other packages. The runtime matches a command against the commands array by exact normalized path, so the array lists the full command paths (github sync, github export, github import, gh-issues import, github project import, github project sync) — not a bare top-level name, which would never match and silently disable the early warning. The authoritative credential gate stays in the command handlers; this only narrows the override's scope."
type: Issue
status: closed
priority: 2
tags: []
created_at: "2026-08-14T05:58:22.786Z"
updated_at: "2026-08-14T06:03:30.132Z"
closed_at: "2026-08-14T06:03:30.121Z"
completed_at: "2026-08-14T06:03:30.121Z"
author: pi-agent
files[2]{path,scope}:
index.ts,project
test/smoke.test.ts,project
tests[1]{command,path,scope,note}:
npm test,test/smoke.test.ts,project,"Asserts registerPreflight is a scoped object whose commands array equals pm-github's owned mutating command paths (github sync, github export, github import, gh-issues import, github project import, github project sync) and run is a function. Test title: preflight override is scoped to pm-github's owned command paths"
close_reason: completed
body: ""
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Fixed

- Scope preflight override to pm-github's owned commands ([pm-github-yhhz](https://github.com/unbraind/pm-github/blob/main/.agents/pm/issues/pm-github-yhhz.toon))

## 2026.8.10 - 2026-08-10

### Fixed
Expand Down
45 changes: 30 additions & 15 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4685,23 +4685,38 @@ export default defineExtension({
]);

// -----------------------------------------------------------------------
// preflight — safe, local guard for mutating github commands.
// Runs before pm core commands; it does NOT make network calls (that would
// be a surprise side effect on every command) and cannot hard-block (the
// runtime swallows preflight throws). It only surfaces a clear, early
// warning when a github mutation is requested without a resolvable token;
// the authoritative validation + non-zero exit lives in the handlers.
// preflight — safe, local guard for mutating github commands. Scoped to
// the command paths pm-github owns (the mutating github/gh-issues paths
// isMutatingGithubCommand recognizes) so it cannot contend with another
// package's preflight override; an unscoped (global) override collides
// pairwise with every other installed package's override (pm health reports
// extension_preflight_override_collision). It runs before those commands;
// it does NOT make network calls (that would be a surprise side effect on
// every command) and cannot hard-block (the runtime swallows preflight
// throws). It only surfaces a clear, early warning when a github mutation
// is requested without a resolvable token; the authoritative validation +
// non-zero exit lives in the handlers.
// -----------------------------------------------------------------------
api.registerPreflight((ctx: PreflightOverrideContext) => {
if (isMutatingGithubCommand(ctx.command, ctx.options || {})) {
if (!resolveGitHubToken()) {
console.error(
"[pm-github preflight] this github command mutates remote state but no GitHub " +
"token is resolvable (GITHUB_TOKEN/GH_TOKEN or `gh auth login`). It will fail.",
);
api.registerPreflight({
commands: [
"github sync",
"github export",
"github import",
"gh-issues import",
"github project import",
"github project sync",
],
run: (ctx: PreflightOverrideContext) => {
if (isMutatingGithubCommand(ctx.command, ctx.options || {})) {
if (!resolveGitHubToken()) {
console.error(
"[pm-github preflight] this github command mutates remote state but no GitHub " +
"token is resolvable (GITHUB_TOKEN/GH_TOKEN or `gh auth login`). It will fail.",
);
}
}
}
return {};
return {};
},
});

// -----------------------------------------------------------------------
Expand Down
33 changes: 33 additions & 0 deletions test/smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,39 @@ test("extension registers at least one capability", async () => {
);
});

test("preflight override is scoped to pm-github's owned command paths", async () => {
// The override MUST register as a scoped object (commands + run), not a bare
// function: a global (unscoped) override collides pairwise with every other
// installed package's preflight override (pm health reports
// extension_preflight_override_collision). The runtime matches a command
// against `commands` by exact normalized path, so the array must list the
// full command paths pm-github mutates — not a bare top-level `github` (which
// never matches `github sync` and would silently disable the early warning).
const ext = await harnessPromise;
const override = ext.assertPreflightOverride();
assert.ok(
Array.isArray(override.commands) && override.commands.length > 0,
"preflight override must register as a scoped object with a non-empty commands array",
);
assert.equal(
typeof override.run,
"function",
"scoped preflight override must expose a run function",
);
assert.deepEqual(
override.commands,
[
"github sync",
"github export",
"github import",
"gh-issues import",
"github project import",
"github project sync",
],
"preflight override must be scoped to exactly pm-github's owned mutating command paths",
);
});

test("parseNextLink extracts the rel=\"next\" page URL", () => {
const header = '<https://api.github.com/repositories/1/issues?page=2>; rel="next", '
+ '<https://api.github.com/repositories/1/issues?page=5>; rel="last"';
Expand Down