@mgiles/perk 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -44
- package/extension/adapters/planAdapterPlannotator.ts +27 -41
- package/extension/adapters/planAdapterTombell.ts +15 -28
- package/extension/adapters/todoAdapterJuicesharp.ts +10 -13
- package/extension/checkpoints/checkpoints.ts +19 -12
- package/extension/doors/address.ts +4 -4
- package/extension/doors/askUser.ts +12 -8
- package/extension/doors/ciExecutor.ts +21 -14
- package/extension/doors/hunkHandoff.ts +202 -0
- package/extension/doors/land.ts +31 -9
- package/extension/doors/learn.ts +2 -2
- package/extension/doors/learnFactory.ts +144 -0
- package/extension/doors/plannotatorHandoff.ts +509 -0
- package/extension/doors/prReview.ts +4 -4
- package/extension/doors/prReviewBrowser.ts +341 -0
- package/extension/doors/prReviewTerminal.ts +267 -0
- package/extension/doors/selfcheck.ts +238 -5
- package/extension/doors/submit.ts +20 -0
- package/extension/doors/submitPrReview.ts +408 -0
- package/extension/factories/objective.ts +15 -5
- package/extension/factories/objectiveAuthor.ts +15 -32
- package/extension/factories/objectiveDraft.ts +1 -1
- package/extension/factories/objectivePlan.ts +12 -10
- package/extension/factories/objectiveSave.ts +2 -2
- package/extension/factories/planMode.ts +22 -40
- package/extension/factories/planReview.ts +213 -191
- package/extension/factories/planSave.ts +7 -7
- package/extension/index.ts +83 -25
- package/extension/substrate/bindingDelivery.ts +32 -10
- package/extension/substrate/bindings.ts +4 -2
- package/extension/substrate/cache.ts +34 -7
- package/extension/substrate/clipboard.ts +81 -0
- package/extension/substrate/config.ts +88 -65
- package/extension/substrate/git.ts +43 -0
- package/extension/substrate/paths.ts +1 -1
- package/extension/substrate/prompts.ts +2 -2
- package/extension/substrate/providers.ts +62 -8
- package/extension/substrate/sessionPointers.ts +35 -6
- package/extension/substrate/structuredOutput.ts +3 -1
- package/extension/substrate/terminalLaunch.ts +178 -0
- package/extension/substrate/toolGating.ts +330 -79
- package/extension/substrate/toolParams.ts +7 -0
- package/extension/substrate/workflowState.ts +54 -2
- package/extension/surfaces/footerProvider.ts +8 -4
- package/extension/surfaces/surfaces.ts +330 -12
- package/extension/vendor/btw/btw.ts +10 -0
- package/extension/worker/readOnlySession.ts +19 -6
- package/extension/worker/worker.ts +77 -7
- package/extension/workerMain.ts +12 -13
- package/package.json +3 -3
- package/prompts/_fixtures/live.yaml +117 -2
- package/prompts/contexts/adapters/juicesharp-todo.md +7 -0
- package/prompts/contexts/adapters/plannotator-objective.md +7 -0
- package/prompts/contexts/adapters/plannotator-plan.md +6 -0
- package/prompts/contexts/adapters/tombell-plan.md +17 -0
- package/prompts/contexts/objective-authoring.md +20 -0
- package/prompts/contexts/plan-authoring.md +24 -0
- package/prompts/contexts/read-only.md +10 -0
- package/prompts/stages/conflict-resolution.md +1 -1
- package/prompts/stages/learn-code.md +1 -1
- package/prompts/stages/learn-docs.md +2 -2
- package/prompts/stages/learn-orchestrate.md +1 -1
- package/prompts/stages/objective-author/adopt.md +1 -1
- package/prompts/stages/objective-author/file.md +1 -1
- package/prompts/stages/objective-plan/guidance.md +1 -1
- package/prompts/stages/objective-plan/seed.md +1 -1
- package/prompts/stages/objective-reconcile.md +1 -1
- package/prompts/stages/objective-replan.md +1 -1
- package/prompts/stages/plan-from/adopt.md +2 -2
- package/prompts/stages/plan-from/file.md +2 -2
- package/prompts/stages/pr-review-browser/active.md +11 -0
- package/prompts/stages/pr-review-browser/foreign.md +11 -0
- package/prompts/stages/pr-review-terminal/active.md +12 -0
- package/prompts/stages/pr-review-terminal/foreign.md +13 -0
- package/prompts/stages/pr-review-terminal/local.md +4 -0
- package/prompts/stages/pr-review.md +1 -1
- package/prompts/stages/replan.md +2 -2
- package/prompts/stages/skills/create-from.md +1 -1
- package/prompts/stages/skills/create.md +1 -1
- package/prompts/stages/skills/refine.md +1 -1
- package/shared/README.md +22 -18
- package/shared/bindings.yaml +10 -2
- package/shared/contracts-history.md +24 -0
- package/shared/contracts.md +1442 -1787
- package/shared/providers.yaml +8 -1
- package/shared/registry.yaml +7 -8
- package/shared/schemas/inputs/review-submit-batch.schema.json +66 -0
- package/shared/schemas/outputs/pr-review-checkout.schema.json +69 -0
- package/shared/schemas/outputs/pr-review-cleanup.schema.json +54 -0
- package/shared/schemas/outputs/pr-review-submit.schema.json +64 -0
- package/extension/doors/learnCode.ts +0 -100
- package/extension/doors/learnDocs.ts +0 -100
- package/extension/doors/prReviewLocal.ts +0 -229
package/shared/providers.yaml
CHANGED
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
# id — the stable provider id. NOT the cache.plan-ref `provider` string:
|
|
17
17
|
# that field is the ISSUE BACKEND (the stamped `backend_id`, e.g.
|
|
18
18
|
# "github"), not the seam id — see contracts.md §8.10 / §8.21.
|
|
19
|
-
# seam — `plan` | `todo` | `askuser` | `footer` | `web` (the five seams
|
|
19
|
+
# seam — `plan` | `todo` | `askuser` | `footer` | `web` (the five seams; see
|
|
20
|
+
# pluggability-taxonomy.md for the scope fence).
|
|
20
21
|
# package — the foreign Pi package spec added to .pi/settings.json `packages`
|
|
21
22
|
# (e.g. "npm:@tombell/pi-plan"); `null` for perk's own bundled reference
|
|
22
23
|
# provider (nothing to add — perk produces the contract natively). NOT
|
|
@@ -95,6 +96,12 @@
|
|
|
95
96
|
# is pi-web-access-specific (it depends on `fetch_content`'s GitHub-clone path) and is accepted as
|
|
96
97
|
# lost under a foreign web selection. Fail-safe: any config-read error resolves to `pi-web-access`,
|
|
97
98
|
# keeping today's behavior exactly.
|
|
99
|
+
#
|
|
100
|
+
# There is NO review seam: the two surface-named review doors (`/pr-review-terminal` — the hunk
|
|
101
|
+
# terminal TUI; `/pr-review-browser` — the plannotator browser UI) are the selection (the command
|
|
102
|
+
# IS the surface pick). The hunk CLI converges unconditionally (init/doctor's best-effort
|
|
103
|
+
# `review-cli` install/verify); the plannotator package rides the plan seam's `plannotator-plan`
|
|
104
|
+
# entry.
|
|
98
105
|
|
|
99
106
|
schema_version: 1
|
|
100
107
|
|
package/shared/registry.yaml
CHANGED
|
@@ -52,12 +52,11 @@ state_keys:
|
|
|
52
52
|
# actual dedicated cold-door routing lives in code via `perk/cli/stages.py` `DEDICATED_STAGES`
|
|
53
53
|
# (the generic launcher generator skips those ids). The field documents intent; it does not route.
|
|
54
54
|
# `doors.cold_remote` records which stages a remote runner may run (cli-vs-pi §4.5) —
|
|
55
|
-
# a
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# Phase 3 *drives* it. Revisable in Phase 3.
|
|
55
|
+
# a settled design decision, not merely "not built yet". It is `true` exactly on the
|
|
56
|
+
# bounded agentic stages (`implement`, `address`), where the goal is already pinned by
|
|
57
|
+
# a plan or by reviewer feedback. The interactive/exploratory stages (planning above
|
|
58
|
+
# all) and the quick deterministic human-gate ops (`submit`/`land`/`learn`) are
|
|
59
|
+
# local-only by design — review, merge, and judgment capture stay where a human is.
|
|
61
60
|
# ---------------------------------------------------------------------------
|
|
62
61
|
stages:
|
|
63
62
|
- id: objective-author
|
|
@@ -129,7 +128,7 @@ stages:
|
|
|
129
128
|
summary: Do the work on a branch (requires fresh context; cold-only).
|
|
130
129
|
mode: read-write
|
|
131
130
|
worktree: create # first stage that needs a branch/worktree (folds in erk's `prepare`)
|
|
132
|
-
doors: { warm: false, cold_local: true, cold_remote: true } #
|
|
131
|
+
doors: { warm: false, cold_local: true, cold_remote: true } # bounded agentic stage — remotely runnable
|
|
133
132
|
run_id: { warm: keep, cold_local: mint, cold_remote: mint }
|
|
134
133
|
command: implement
|
|
135
134
|
requires: [cache.plan-ref] # T4: cold door derives plan-<pr_id> from the active ref
|
|
@@ -155,7 +154,7 @@ stages:
|
|
|
155
154
|
summary: Classify PR review feedback (isolated child) and resolve the threads.
|
|
156
155
|
mode: read-write
|
|
157
156
|
worktree: reuse
|
|
158
|
-
doors: { warm: true, cold_local: true, cold_remote: true } #
|
|
157
|
+
doors: { warm: true, cold_local: true, cold_remote: true } # bounded agentic stage — remotely runnable
|
|
159
158
|
run_id: { warm: keep, cold_local: mint, cold_remote: mint }
|
|
160
159
|
command: pr address
|
|
161
160
|
# P2.T7: a spawned read-only child (borrowed pi-subagents) fetches + classifies feedback;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$defs": {
|
|
3
|
+
"ReviewSubmitCommentInput": {
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"description": "One inline comment in the machine-authored submission batch (strict: a typo fails loudly).\n\n`line` is **non-nullable** by design: unanchorable (`line: null`) findings are folded into\nthe review body upstream, during the triage curation \u2014 never submitted as inline comments.",
|
|
6
|
+
"properties": {
|
|
7
|
+
"path": {
|
|
8
|
+
"minLength": 1,
|
|
9
|
+
"title": "Path",
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"line": {
|
|
13
|
+
"title": "Line",
|
|
14
|
+
"type": "integer"
|
|
15
|
+
},
|
|
16
|
+
"side": {
|
|
17
|
+
"default": "RIGHT",
|
|
18
|
+
"enum": [
|
|
19
|
+
"LEFT",
|
|
20
|
+
"RIGHT"
|
|
21
|
+
],
|
|
22
|
+
"title": "Side",
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
"body": {
|
|
26
|
+
"minLength": 1,
|
|
27
|
+
"title": "Body",
|
|
28
|
+
"type": "string"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"required": [
|
|
32
|
+
"path",
|
|
33
|
+
"line",
|
|
34
|
+
"body"
|
|
35
|
+
],
|
|
36
|
+
"title": "ReviewSubmitCommentInput",
|
|
37
|
+
"type": "object"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"additionalProperties": false,
|
|
41
|
+
"description": "The strict `review-submit` batch shape (`{body, comments?}` \u2014 the event rides the flag).\n\nNo `fyi` field: in-session triage color is structurally unpostable through this door\n(strict extra-forbid rejects it). `comments` stays nullable so an explicit `null` is\ntolerated, normalized to `[]` in conversion.",
|
|
42
|
+
"properties": {
|
|
43
|
+
"body": {
|
|
44
|
+
"default": "",
|
|
45
|
+
"title": "Body",
|
|
46
|
+
"type": "string"
|
|
47
|
+
},
|
|
48
|
+
"comments": {
|
|
49
|
+
"anyOf": [
|
|
50
|
+
{
|
|
51
|
+
"items": {
|
|
52
|
+
"$ref": "#/$defs/ReviewSubmitCommentInput"
|
|
53
|
+
},
|
|
54
|
+
"type": "array"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "null"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"default": null,
|
|
61
|
+
"title": "Comments"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"title": "ReviewSubmitBatchInput",
|
|
65
|
+
"type": "object"
|
|
66
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"additionalProperties": false,
|
|
3
|
+
"description": "The ``--json`` serialization boundary of :class:`ReviewCheckoutResult` (flat; envelope\nkeys first). ``path`` is absolute; the SHAs are full 40-char.",
|
|
4
|
+
"properties": {
|
|
5
|
+
"success": {
|
|
6
|
+
"title": "Success",
|
|
7
|
+
"type": "boolean"
|
|
8
|
+
},
|
|
9
|
+
"error_type": {
|
|
10
|
+
"anyOf": [
|
|
11
|
+
{
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"type": "null"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"title": "Error Type"
|
|
19
|
+
},
|
|
20
|
+
"message": {
|
|
21
|
+
"anyOf": [
|
|
22
|
+
{
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "null"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"title": "Message"
|
|
30
|
+
},
|
|
31
|
+
"path": {
|
|
32
|
+
"title": "Path",
|
|
33
|
+
"type": "string"
|
|
34
|
+
},
|
|
35
|
+
"pr": {
|
|
36
|
+
"title": "Pr",
|
|
37
|
+
"type": "integer"
|
|
38
|
+
},
|
|
39
|
+
"url": {
|
|
40
|
+
"title": "Url",
|
|
41
|
+
"type": "string"
|
|
42
|
+
},
|
|
43
|
+
"head_sha": {
|
|
44
|
+
"title": "Head Sha",
|
|
45
|
+
"type": "string"
|
|
46
|
+
},
|
|
47
|
+
"base_sha": {
|
|
48
|
+
"title": "Base Sha",
|
|
49
|
+
"type": "string"
|
|
50
|
+
},
|
|
51
|
+
"base_ref": {
|
|
52
|
+
"title": "Base Ref",
|
|
53
|
+
"type": "string"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"required": [
|
|
57
|
+
"success",
|
|
58
|
+
"error_type",
|
|
59
|
+
"message",
|
|
60
|
+
"path",
|
|
61
|
+
"pr",
|
|
62
|
+
"url",
|
|
63
|
+
"head_sha",
|
|
64
|
+
"base_sha",
|
|
65
|
+
"base_ref"
|
|
66
|
+
],
|
|
67
|
+
"title": "PrReviewCheckoutOut",
|
|
68
|
+
"type": "object"
|
|
69
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"additionalProperties": false,
|
|
3
|
+
"description": "The ``--json`` serialization boundary of :class:`ReviewCleanupResult` (flat; envelope\nkeys first). ``path`` is absolute.",
|
|
4
|
+
"properties": {
|
|
5
|
+
"success": {
|
|
6
|
+
"title": "Success",
|
|
7
|
+
"type": "boolean"
|
|
8
|
+
},
|
|
9
|
+
"error_type": {
|
|
10
|
+
"anyOf": [
|
|
11
|
+
{
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"type": "null"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"title": "Error Type"
|
|
19
|
+
},
|
|
20
|
+
"message": {
|
|
21
|
+
"anyOf": [
|
|
22
|
+
{
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "null"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"title": "Message"
|
|
30
|
+
},
|
|
31
|
+
"pr": {
|
|
32
|
+
"title": "Pr",
|
|
33
|
+
"type": "integer"
|
|
34
|
+
},
|
|
35
|
+
"path": {
|
|
36
|
+
"title": "Path",
|
|
37
|
+
"type": "string"
|
|
38
|
+
},
|
|
39
|
+
"removed": {
|
|
40
|
+
"title": "Removed",
|
|
41
|
+
"type": "boolean"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"required": [
|
|
45
|
+
"success",
|
|
46
|
+
"error_type",
|
|
47
|
+
"message",
|
|
48
|
+
"pr",
|
|
49
|
+
"path",
|
|
50
|
+
"removed"
|
|
51
|
+
],
|
|
52
|
+
"title": "PrReviewCleanupOut",
|
|
53
|
+
"type": "object"
|
|
54
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"additionalProperties": false,
|
|
3
|
+
"description": "The ``--json`` serialization boundary of the submission result (flat; envelope keys\nfirst). ``event`` is the flag spelling; ``mode`` \u2208\n``validated | review | review_folded | comment_fallback``.",
|
|
4
|
+
"properties": {
|
|
5
|
+
"success": {
|
|
6
|
+
"title": "Success",
|
|
7
|
+
"type": "boolean"
|
|
8
|
+
},
|
|
9
|
+
"error_type": {
|
|
10
|
+
"anyOf": [
|
|
11
|
+
{
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"type": "null"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"title": "Error Type"
|
|
19
|
+
},
|
|
20
|
+
"message": {
|
|
21
|
+
"anyOf": [
|
|
22
|
+
{
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"type": "null"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"title": "Message"
|
|
30
|
+
},
|
|
31
|
+
"dry_run": {
|
|
32
|
+
"title": "Dry Run",
|
|
33
|
+
"type": "boolean"
|
|
34
|
+
},
|
|
35
|
+
"pr": {
|
|
36
|
+
"title": "Pr",
|
|
37
|
+
"type": "integer"
|
|
38
|
+
},
|
|
39
|
+
"event": {
|
|
40
|
+
"title": "Event",
|
|
41
|
+
"type": "string"
|
|
42
|
+
},
|
|
43
|
+
"mode": {
|
|
44
|
+
"title": "Mode",
|
|
45
|
+
"type": "string"
|
|
46
|
+
},
|
|
47
|
+
"comment_count": {
|
|
48
|
+
"title": "Comment Count",
|
|
49
|
+
"type": "integer"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"required": [
|
|
53
|
+
"success",
|
|
54
|
+
"error_type",
|
|
55
|
+
"message",
|
|
56
|
+
"dry_run",
|
|
57
|
+
"pr",
|
|
58
|
+
"event",
|
|
59
|
+
"mode",
|
|
60
|
+
"comment_count"
|
|
61
|
+
],
|
|
62
|
+
"title": "PrReviewSubmitOut",
|
|
63
|
+
"type": "object"
|
|
64
|
+
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
// hop-2 — the learn-code plan factory's warm transition surface: the `/learn-code` command.
|
|
2
|
-
//
|
|
3
|
-
// The warm twin of the `perk learn code` cold door (sibling of `/learn-docs`). It DELEGATES the
|
|
4
|
-
// gather to the Python plane (`perk learn code --gather --json` via the shared cold-door client
|
|
5
|
-
// `runColdDoor` — gate-safe, not subject to the read-only bash allowlist), decodes
|
|
6
|
-
// `{ inbox_path, learn_numbers }`, then injects the factory guidance via `pi.sendUserMessage` so
|
|
7
|
-
// the model reads the inbox, authors a code plan, and calls `plan_save` with `consumed_learn`. No
|
|
8
|
-
// model tool — the model uses the existing `plan_save` tool.
|
|
9
|
-
//
|
|
10
|
-
// Headless-safe: rich UI is guarded by `ctx.hasUI`; without a UI it logs to stderr and returns
|
|
11
|
-
// (the gather still runs so the inbox is materialized, but no turn is driven).
|
|
12
|
-
|
|
13
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import { bindingSuffix } from "../substrate/bindingDelivery.ts";
|
|
15
|
-
import { type ColdJson, runColdDoor, stringField } from "../substrate/coldDoor.ts";
|
|
16
|
-
import { registerPerkCommand } from "../substrate/command.ts";
|
|
17
|
-
import { render } from "../substrate/prompts.ts";
|
|
18
|
-
import { report } from "../surfaces/report.ts";
|
|
19
|
-
|
|
20
|
-
/** The decoded `perk learn code --gather --json` payload slice the warm door consumes. */
|
|
21
|
-
interface LearnCodeGatherPayload {
|
|
22
|
-
inbox_path: string;
|
|
23
|
-
/** Opaque string learn-issue ids (GitHub "45", Linear "ENG-45") — §8.21. */
|
|
24
|
-
learn_numbers: string[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Strict decode — the guidance dereferences both fields; `launched` is unconsumed. Exported for offline reject-branch tests. */
|
|
28
|
-
export function decodeGather(payload: ColdJson): LearnCodeGatherPayload | null {
|
|
29
|
-
const inboxPath = stringField(payload, "inbox_path");
|
|
30
|
-
const numbers = payload.learn_numbers;
|
|
31
|
-
if (inboxPath === undefined) return null;
|
|
32
|
-
// String ids are canonical (§8.21); numbers are tolerated + coerced (older envelopes).
|
|
33
|
-
if (
|
|
34
|
-
!Array.isArray(numbers) ||
|
|
35
|
-
!numbers.every((n) => typeof n === "string" || typeof n === "number")
|
|
36
|
-
) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
return { inbox_path: inboxPath, learn_numbers: numbers.map((n) => String(n)) };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* The seed guidance the warm `/learn-code` injects to start the factory loop (the perk-learn-code
|
|
44
|
-
* skill pointer rides the skill-binding suffix — not hardcoded here). Pure + exported
|
|
45
|
-
* for offline tests.
|
|
46
|
-
*/
|
|
47
|
-
export function learnCodeGuidance(inboxPath: string, learnNumbers: string[]): string {
|
|
48
|
-
return render("stages/learn-code.md", {
|
|
49
|
-
inbox_path: inboxPath,
|
|
50
|
-
num_list: learnNumbers.join(", "),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Register the warm learn-code door: the `/learn-code` command (no model tool). */
|
|
55
|
-
export function registerLearnCode(pi: ExtensionAPI): void {
|
|
56
|
-
registerPerkCommand(pi, "learn-code", {
|
|
57
|
-
description:
|
|
58
|
-
"Start the learn-code plan factory: gather pre-stamped SHOULD_BE_CODE perk:learn issues into " +
|
|
59
|
-
"an inbox and author a plan routing each into its real code home.",
|
|
60
|
-
handler: async (_args, ctx: ExtensionContext) => {
|
|
61
|
-
// Report-only door (no Result type): branch on `errorType` directly (the coldDoor header
|
|
62
|
-
// convention). A clean "nothing to route" exits non-zero with error_type=no_learn_issues —
|
|
63
|
-
// the client's envelope-aware arm surfaces it gently.
|
|
64
|
-
const r = await runColdDoor<LearnCodeGatherPayload>(
|
|
65
|
-
pi,
|
|
66
|
-
ctx,
|
|
67
|
-
["learn", "code", "--gather", "--json"],
|
|
68
|
-
{ label: "perk learn code", decode: decodeGather },
|
|
69
|
-
);
|
|
70
|
-
if (!r.ok) {
|
|
71
|
-
if (r.errorType === "no_learn_issues") {
|
|
72
|
-
report(
|
|
73
|
-
ctx,
|
|
74
|
-
"learn-code",
|
|
75
|
-
"warning",
|
|
76
|
-
"nothing to route into code (no SHOULD_BE_CODE perk:learn issues).",
|
|
77
|
-
);
|
|
78
|
-
} else {
|
|
79
|
-
report(ctx, "learn-code", "error", `gather failed: ${r.message}`);
|
|
80
|
-
}
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!ctx.hasUI) {
|
|
85
|
-
// Headless can't drive a turn — the inbox is materialized; log and return (fail-safe).
|
|
86
|
-
console.error(
|
|
87
|
-
`perk: /learn-code invoked (headless) — gathered ${r.data.learn_numbers.length} ` +
|
|
88
|
-
`learn issue(s) into ${r.data.inbox_path}; run interactively to author the code plan.`,
|
|
89
|
-
);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
report(ctx, "learn-code", "info", `gathered ${r.data.learn_numbers.length} learn issue(s)`);
|
|
94
|
-
pi.sendUserMessage(
|
|
95
|
-
learnCodeGuidance(r.data.inbox_path, r.data.learn_numbers) +
|
|
96
|
-
bindingSuffix(ctx.cwd, "command:learn-code"),
|
|
97
|
-
);
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
// hop-2 — the learned-docs plan factory's warm transition surface: the `/learn-docs` command.
|
|
2
|
-
//
|
|
3
|
-
// The warm twin of the `perk learn docs` cold door. It DELEGATES the gather to the Python plane
|
|
4
|
-
// (`perk learn docs --gather --json` via the shared cold-door client `runColdDoor` — gate-safe,
|
|
5
|
-
// not subject to the read-only bash allowlist), decodes `{ inbox_path, learn_numbers }`, then
|
|
6
|
-
// injects the factory guidance via
|
|
7
|
-
// `pi.sendUserMessage` so the model reads the inbox, authors a docs plan, and calls `plan_save`
|
|
8
|
-
// with `consumed_learn`. No model tool — the model uses the existing `plan_save` tool.
|
|
9
|
-
//
|
|
10
|
-
// Headless-safe: rich UI is guarded by `ctx.hasUI`; without a UI it logs to stderr and returns
|
|
11
|
-
// (the gather still runs so the inbox is materialized, but no turn is driven).
|
|
12
|
-
|
|
13
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import { bindingSuffix } from "../substrate/bindingDelivery.ts";
|
|
15
|
-
import { type ColdJson, runColdDoor, stringField } from "../substrate/coldDoor.ts";
|
|
16
|
-
import { registerPerkCommand } from "../substrate/command.ts";
|
|
17
|
-
import { render } from "../substrate/prompts.ts";
|
|
18
|
-
import { report } from "../surfaces/report.ts";
|
|
19
|
-
|
|
20
|
-
/** The decoded `perk learn docs --gather --json` payload slice the warm door consumes. */
|
|
21
|
-
interface LearnDocsGatherPayload {
|
|
22
|
-
inbox_path: string;
|
|
23
|
-
/** Opaque string learn-issue ids (GitHub "45", Linear "ENG-45") — §8.21. */
|
|
24
|
-
learn_numbers: string[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Strict decode — the guidance dereferences both fields; `launched` is unconsumed. Exported for offline reject-branch tests. */
|
|
28
|
-
export function decodeGather(payload: ColdJson): LearnDocsGatherPayload | null {
|
|
29
|
-
const inboxPath = stringField(payload, "inbox_path");
|
|
30
|
-
const numbers = payload.learn_numbers;
|
|
31
|
-
if (inboxPath === undefined) return null;
|
|
32
|
-
// String ids are canonical (§8.21); numbers are tolerated + coerced (older envelopes).
|
|
33
|
-
if (
|
|
34
|
-
!Array.isArray(numbers) ||
|
|
35
|
-
!numbers.every((n) => typeof n === "string" || typeof n === "number")
|
|
36
|
-
) {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
return { inbox_path: inboxPath, learn_numbers: numbers.map((n) => String(n)) };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* The seed guidance the warm `/learn-docs` injects to start the factory loop (the perk-learn-docs
|
|
44
|
-
* skill pointer rides the skill-binding suffix — not hardcoded here). Pure + exported
|
|
45
|
-
* for offline tests.
|
|
46
|
-
*/
|
|
47
|
-
export function learnDocsGuidance(inboxPath: string, learnNumbers: string[]): string {
|
|
48
|
-
return render("stages/learn-docs.md", {
|
|
49
|
-
inbox_path: inboxPath,
|
|
50
|
-
num_list: learnNumbers.join(", "),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Register the warm learned-docs door: the `/learn-docs` command (no model tool). */
|
|
55
|
-
export function registerLearnDocs(pi: ExtensionAPI): void {
|
|
56
|
-
registerPerkCommand(pi, "learn-docs", {
|
|
57
|
-
description:
|
|
58
|
-
"Start the learned-docs plan factory: gather open perk:learn issues into an inbox and author " +
|
|
59
|
-
"a docs/learned consolidation plan.",
|
|
60
|
-
handler: async (_args, ctx: ExtensionContext) => {
|
|
61
|
-
// Report-only door (no Result type): branch on `errorType` directly (the coldDoor header
|
|
62
|
-
// convention). A clean "nothing to consolidate" exits non-zero with
|
|
63
|
-
// error_type=no_learn_issues — the client's envelope-aware arm surfaces it gently.
|
|
64
|
-
const r = await runColdDoor<LearnDocsGatherPayload>(
|
|
65
|
-
pi,
|
|
66
|
-
ctx,
|
|
67
|
-
["learn", "docs", "--gather", "--json"],
|
|
68
|
-
{ label: "perk learn docs", decode: decodeGather },
|
|
69
|
-
);
|
|
70
|
-
if (!r.ok) {
|
|
71
|
-
if (r.errorType === "no_learn_issues") {
|
|
72
|
-
report(
|
|
73
|
-
ctx,
|
|
74
|
-
"learn-docs",
|
|
75
|
-
"warning",
|
|
76
|
-
"nothing to consolidate (no open perk:learn issues).",
|
|
77
|
-
);
|
|
78
|
-
} else {
|
|
79
|
-
report(ctx, "learn-docs", "error", `gather failed: ${r.message}`);
|
|
80
|
-
}
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!ctx.hasUI) {
|
|
85
|
-
// Headless can't drive a turn — the inbox is materialized; log and return (fail-safe).
|
|
86
|
-
console.error(
|
|
87
|
-
`perk: /learn-docs invoked (headless) — gathered ${r.data.learn_numbers.length} ` +
|
|
88
|
-
`learn issue(s) into ${r.data.inbox_path}; run interactively to author the docs plan.`,
|
|
89
|
-
);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
report(ctx, "learn-docs", "info", `gathered ${r.data.learn_numbers.length} learn issue(s)`);
|
|
94
|
-
pi.sendUserMessage(
|
|
95
|
-
learnDocsGuidance(r.data.inbox_path, r.data.learn_numbers) +
|
|
96
|
-
bindingSuffix(ctx.cwd, "command:learn-docs"),
|
|
97
|
-
);
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
}
|