peaks-cli 1.2.5 → 1.2.6
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/dist/src/cli/commands/workspace-commands.js +14 -2
- package/dist/src/services/config/config-safety.d.ts +26 -0
- package/dist/src/services/config/config-safety.js +76 -0
- package/dist/src/services/config/config-service.d.ts +1 -1
- package/dist/src/services/config/config-service.js +2 -2
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +1 -1
- package/skills/peaks-solo/SKILL.md +1 -1
- package/skills/peaks-solo/references/a2a-artifact-mapping.md +115 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { initWorkspace, InvalidSessionIdError, ConflictingSessionError } from '../../services/workspace/workspace-service.js';
|
|
2
2
|
import { ensureSession } from '../../services/session/session-manager.js';
|
|
3
|
+
import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
|
|
3
4
|
import { fail, ok } from '../../shared/result.js';
|
|
4
5
|
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
5
6
|
export function registerWorkspaceCommands(program, io) {
|
|
@@ -18,15 +19,26 @@ export function registerWorkspaceCommands(program, io) {
|
|
|
18
19
|
// - omitted: defer to ensureSession(), which reuses an existing
|
|
19
20
|
// binding or auto-generates a fresh one. The init then writes
|
|
20
21
|
// .session.json so the binding sticks.
|
|
22
|
+
//
|
|
23
|
+
// Before that: canonicalise the project root. If the user (or the
|
|
24
|
+
// LLM via "$(pwd)") passed a sub-directory of a real git repo
|
|
25
|
+
// (e.g. prompt-project/prompt-project/ inside the outer
|
|
26
|
+
// prompt-project/.git), promote the path to the git root. Without
|
|
27
|
+
// this, peaks would build a parallel .peaks/ tree under the
|
|
28
|
+
// nested sub-folder and silently break the project-binding model
|
|
29
|
+
// (the same regression that produced prompt-project/.peaks/ in
|
|
30
|
+
// the 5/27-5/29 sessions). When startPath is not inside any
|
|
31
|
+
// git repo, the helper falls through to the cwd verbatim.
|
|
32
|
+
const projectRoot = resolveCanonicalProjectRoot(options.project);
|
|
21
33
|
let sessionId;
|
|
22
34
|
if (options.sessionId !== undefined && options.sessionId.length > 0) {
|
|
23
35
|
sessionId = options.sessionId;
|
|
24
36
|
}
|
|
25
37
|
else {
|
|
26
|
-
sessionId = await ensureSession(
|
|
38
|
+
sessionId = await ensureSession(projectRoot);
|
|
27
39
|
}
|
|
28
40
|
const report = await initWorkspace({
|
|
29
|
-
projectRoot
|
|
41
|
+
projectRoot,
|
|
30
42
|
sessionId,
|
|
31
43
|
allowSessionRebind: options.allowSessionRebind === true
|
|
32
44
|
});
|
|
@@ -2,6 +2,32 @@ export declare function getUserConfigPath(): string;
|
|
|
2
2
|
export declare function isInsidePath(childPath: string, parentPath: string): boolean;
|
|
3
3
|
export declare function findProjectRoot(startPath: string): string | null;
|
|
4
4
|
export declare function resolveProjectRootForConfig(startPath: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Canonicalise a user-supplied project root path against git's view of the
|
|
7
|
+
* repository root. This is the fix for the nested-directory regression
|
|
8
|
+
* where peaks-cli would write `.peaks/` under a nested sub-folder
|
|
9
|
+
* (e.g. `prompt-project/prompt-project/.peaks/`) because the LLM passed
|
|
10
|
+
* `$(pwd)` from inside a sub-directory of a real git repo. Without
|
|
11
|
+
* canonicalisation, peaks accepted the cwd as-is, built the .peaks/
|
|
12
|
+
* tree there, and left the team with two parallel state stores.
|
|
13
|
+
*
|
|
14
|
+
* Strategy:
|
|
15
|
+
* 1. If `startPath` (or any ancestor) is inside a git repo, return
|
|
16
|
+
* `git rev-parse --show-toplevel` from `startPath`. The git root
|
|
17
|
+
* is the *only* correct answer for "where does the .peaks/ tree
|
|
18
|
+
* belong?" — sub-folders of a git repo are not their own projects.
|
|
19
|
+
* 2. If `startPath` is not inside a git repo, fall back to
|
|
20
|
+
* `findProjectRoot` (the existing heuristic) so the CLI still
|
|
21
|
+
* works for non-git projects.
|
|
22
|
+
* 3. If both fail, return `startPath` unchanged — better to write
|
|
23
|
+
* to the cwd than to refuse the command.
|
|
24
|
+
*
|
|
25
|
+
* This is intentionally fail-open: it only *promotes* a path towards
|
|
26
|
+
* the git root, it never demotes one. A non-git user is unaffected.
|
|
27
|
+
* The function does NOT throw on a missing git binary or a non-zero
|
|
28
|
+
* `git rev-parse` exit; both fall through to the heuristic.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveCanonicalProjectRoot(startPath: string): string;
|
|
5
31
|
export declare function getProjectConfigPath(projectRoot: string | null): string | null;
|
|
6
32
|
export declare function getProjectBootstrapConfigPath(projectRoot: string): string;
|
|
7
33
|
export declare function validateProjectBootstrapConfigPathForWrite(projectRoot: string, configPath: string): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { closeSync, constants, existsSync, fchmodSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
2
3
|
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
4
5
|
import { homedir } from 'node:os';
|
|
@@ -89,6 +90,81 @@ export function resolveProjectRootForConfig(startPath) {
|
|
|
89
90
|
}
|
|
90
91
|
return pkgRoot ?? start;
|
|
91
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Canonicalise a user-supplied project root path against git's view of the
|
|
95
|
+
* repository root. This is the fix for the nested-directory regression
|
|
96
|
+
* where peaks-cli would write `.peaks/` under a nested sub-folder
|
|
97
|
+
* (e.g. `prompt-project/prompt-project/.peaks/`) because the LLM passed
|
|
98
|
+
* `$(pwd)` from inside a sub-directory of a real git repo. Without
|
|
99
|
+
* canonicalisation, peaks accepted the cwd as-is, built the .peaks/
|
|
100
|
+
* tree there, and left the team with two parallel state stores.
|
|
101
|
+
*
|
|
102
|
+
* Strategy:
|
|
103
|
+
* 1. If `startPath` (or any ancestor) is inside a git repo, return
|
|
104
|
+
* `git rev-parse --show-toplevel` from `startPath`. The git root
|
|
105
|
+
* is the *only* correct answer for "where does the .peaks/ tree
|
|
106
|
+
* belong?" — sub-folders of a git repo are not their own projects.
|
|
107
|
+
* 2. If `startPath` is not inside a git repo, fall back to
|
|
108
|
+
* `findProjectRoot` (the existing heuristic) so the CLI still
|
|
109
|
+
* works for non-git projects.
|
|
110
|
+
* 3. If both fail, return `startPath` unchanged — better to write
|
|
111
|
+
* to the cwd than to refuse the command.
|
|
112
|
+
*
|
|
113
|
+
* This is intentionally fail-open: it only *promotes* a path towards
|
|
114
|
+
* the git root, it never demotes one. A non-git user is unaffected.
|
|
115
|
+
* The function does NOT throw on a missing git binary or a non-zero
|
|
116
|
+
* `git rev-parse` exit; both fall through to the heuristic.
|
|
117
|
+
*/
|
|
118
|
+
export function resolveCanonicalProjectRoot(startPath) {
|
|
119
|
+
const start = resolve(startPath);
|
|
120
|
+
const gitRoot = resolveProjectRootFromGit(start);
|
|
121
|
+
if (gitRoot !== null) {
|
|
122
|
+
return gitRoot;
|
|
123
|
+
}
|
|
124
|
+
// Non-git fallback: walk the heuristic up to the home boundary.
|
|
125
|
+
// We do NOT call realpathSync on the heuristic result because the
|
|
126
|
+
// heuristic may legitimately return a path through a symlink that
|
|
127
|
+
// the caller passed in (no canonicalisation needed in that case).
|
|
128
|
+
const heuristicRoot = findProjectRoot(start);
|
|
129
|
+
if (heuristicRoot !== null) {
|
|
130
|
+
return heuristicRoot;
|
|
131
|
+
}
|
|
132
|
+
return start;
|
|
133
|
+
}
|
|
134
|
+
function resolveProjectRootFromGit(startPath) {
|
|
135
|
+
// execFileSync (not execSync) so a malicious `startPath` cannot
|
|
136
|
+
// inject argv into the spawned git invocation. The child only
|
|
137
|
+
// receives `startPath` as the cwd, never as a flag.
|
|
138
|
+
let rawRoot;
|
|
139
|
+
try {
|
|
140
|
+
const stdout = execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
141
|
+
cwd: startPath,
|
|
142
|
+
encoding: 'utf8',
|
|
143
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
144
|
+
});
|
|
145
|
+
const trimmed = stdout.trim();
|
|
146
|
+
if (trimmed.length === 0)
|
|
147
|
+
return null;
|
|
148
|
+
rawRoot = resolve(trimmed);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// git not on PATH, startPath is not in a repo, or some other
|
|
152
|
+
// benign failure — fall through to the heuristic.
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
// On macOS, /tmp is a symlink to /private/tmp; git returns the
|
|
156
|
+
// realpath. If the caller passed a path through the symlink, the
|
|
157
|
+
// two strings won't match byte-for-byte even though they refer
|
|
158
|
+
// to the same directory. realpathSync the git root and the
|
|
159
|
+
// startPath through the same lens so callers get a canonical
|
|
160
|
+
// answer that compares equal to the path they passed in.
|
|
161
|
+
try {
|
|
162
|
+
return realpathSync(rawRoot);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return rawRoot;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
92
168
|
export function getProjectConfigPath(projectRoot) {
|
|
93
169
|
if (!projectRoot)
|
|
94
170
|
return null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConfigGetOptions, ConfigLayer, ConfigSetOptions, MiniMaxProviderConfig, PeaksConfig, TokenRef, WorkspaceConfig } from './config-types.js';
|
|
2
|
-
export { resolveProjectRootForConfig } from './config-safety.js';
|
|
2
|
+
export { resolveProjectRootForConfig, resolveCanonicalProjectRoot } from './config-safety.js';
|
|
3
3
|
export declare function isConfigLayer(value: string): value is ConfigLayer;
|
|
4
4
|
export declare function isSensitiveConfigPath(path: string): boolean;
|
|
5
5
|
export declare function containsSensitiveConfigValue(value: unknown): boolean;
|
|
@@ -3,8 +3,8 @@ import { dirname, isAbsolute, resolve } from 'node:path';
|
|
|
3
3
|
import { DEFAULT_CONFIG } from './config-types.js';
|
|
4
4
|
import { stablePath } from '../../shared/path-utils.js';
|
|
5
5
|
import { findProjectRoot, getProjectBootstrapConfigPath, getProjectConfigPath, getUserConfigPath, isInsidePath, readConfigFileSafely, resolveProjectRootForConfig, validateArtifactWorkspaceMarkerPath, validateArtifactWorkspaceRoot, validateProjectBootstrapConfigPathForWrite, validateUserConfigPathForWrite, writeConfigFileSafely, writeProjectConfigFile, writeUserConfigFile } from './config-safety.js';
|
|
6
|
-
// Re-export resolveProjectRootForConfig for external consumers
|
|
7
|
-
export { resolveProjectRootForConfig } from './config-safety.js';
|
|
6
|
+
// Re-export resolveProjectRootForConfig and resolveCanonicalProjectRoot for external consumers
|
|
7
|
+
export { resolveProjectRootForConfig, resolveCanonicalProjectRoot } from './config-safety.js';
|
|
8
8
|
function readJsonFile(path, validateBeforeRead, errorMessage = 'Config path must stay inside the config root') {
|
|
9
9
|
if (!path || !existsSync(path))
|
|
10
10
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "1.2.
|
|
1
|
+
export declare const CLI_VERSION = "1.2.6";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "1.2.
|
|
1
|
+
export const CLI_VERSION = "1.2.6";
|
package/package.json
CHANGED
|
@@ -1065,4 +1065,4 @@ Do not run upstream installer flows, mutate agent settings, or commit `.codegrap
|
|
|
1065
1065
|
|
|
1066
1066
|
**MCP lifecycle**: `list → plan → apply --yes → call → rollback`. `apply` backs up settings and refuses non-peaks entries unless `--claim` is passed.
|
|
1067
1067
|
|
|
1068
|
-
Detailed rules: `references/external-skill-invocation.md`, `references/openspec-mcp-workflow.md`, `references/workflow.md`, `references/existing-system-extraction.md`.
|
|
1068
|
+
Detailed rules: `references/external-skill-invocation.md`, `references/openspec-mcp-workflow.md`, `references/workflow.md`, `references/existing-system-extraction.md`. For an informational mapping of peaks artefact paths to the A2A (Agent2Agent) protocol's Task / Artifact / Part / Message / AgentCard vocabulary (no A2A implementation, just a shared naming layer), see `references/a2a-artifact-mapping.md`.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# A2A artifact mapping (informational)
|
|
2
|
+
|
|
3
|
+
> Reference for `peaks-solo` and any other peaks skill that produces durable artefacts in `.peaks/<session-id>/`. Maps peaks's on-disk artefact vocabulary onto the A2A (Agent2Agent) protocol's vocabulary so a future peaks consumer (e.g. an external LLM agent or a downstream peaks-cli extension) can read peaks output without having to learn a brand-new schema. This is a **documentation mapping**, not a protocol implementation: peaks-cli does not speak A2A over HTTP, does not host an AgentCard endpoint, and does not advertise its capabilities via A2A's discovery mechanism. It only uses A2A's *concepts* as a shared naming layer.
|
|
4
|
+
|
|
5
|
+
## 1. Why this reference exists
|
|
6
|
+
|
|
7
|
+
The A2A protocol (https://a2acn.com) defines five core concepts: **AgentCard**, **Task**, **Artifact**, **Message**, and **Part**. peaks-cli's session workspace is a parallel vocabulary that grew up independently: `prd/requests/<rid>.md`, `rd/tech-doc.md`, `qa/test-cases/<rid>.md`, etc. The two vocabularies are *not* identical (A2A is HTTP-shaped, peaks is filesystem-shaped), but the A2A concepts are close enough that aligning peaks artefact names with A2A terms in this reference:
|
|
8
|
+
|
|
9
|
+
- gives an external consumer a single translation table instead of two schemas to learn,
|
|
10
|
+
- lets a peaks operator talk about "the artifact" or "the task" in mixed conversations without losing precision,
|
|
11
|
+
- documents what peaks output is **not** (no SSE streaming, no remote AgentCard), so the limits are explicit.
|
|
12
|
+
|
|
13
|
+
This is the kind of borrowing that costs zero code and earns some interoperability. It is **not** an integration: peaks-cli does not implement A2A, does not run an A2A server, and does not depend on the a2a-protocol package. Adopting A2A concepts here is the same as adopting any other shared nomenclature (UML, OpenTelemetry, etc.): it improves the conversation, nothing more.
|
|
14
|
+
|
|
15
|
+
## 2. Concept-to-path mapping
|
|
16
|
+
|
|
17
|
+
The mapping below uses peaks's own paths verbatim. Each row also notes where peaks **diverges** from A2A, so a reader does not assume parity.
|
|
18
|
+
|
|
19
|
+
| A2A concept | peaks artefact | Path (under `.peaks/<session-id>/`) | Notes |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| **AgentCard** (capability advertisement) | `peaks-skill-output-style` + `.peaks/.active-skill.json` | `.peaks/.active-skill.json`, `.peaks/.session.json` | peaks is a *local* tool, not a service. The "card" is the active-skill file plus a peek at `.peaks/PROJECT.md` for human-readable history. There is no `/.well-known/agent-card.json` endpoint. |
|
|
22
|
+
| **Task** (stateful unit of work) | `peaks request` state machine for a single `<rid>` | `.peaks/<sid>/{prd,rd,qa,ui,sc}/requests/<rid>.md` (the request artefact); `.peaks/<sid>/<role>/session.json` (per-session metadata) | peaks's task lifecycle is `prd:confirmed-by-user → handed-off`, then per role `draft → spec-locked → implemented → qa-handoff`, then `qa:running → verdict-issued`. The full state graph is enforced by `peaks request transition`. A2A's Task object is JSON; peaks's task is **a set of files with a `state` field per role**. |
|
|
23
|
+
| **Artifact** (immutable output) | `rd/tech-doc.md`, `rd/code-review.md`, `rd/security-review.md`, `qa/test-cases/<rid>.md`, `qa/test-reports/<rid>.md`, `qa/security-findings.md`, `qa/performance-findings.md`, `sc/handoff.md` | as listed | peaks's artefacts are *append-once*, not strictly immutable: a `qa/test-reports/<rid>.md` may be re-emitted on repair cycles. The convention is "newest write wins; the file at the end of the workflow is the truth", which is close enough to A2A's immutable-Artifact semantics for translation purposes. |
|
|
24
|
+
| **Message** (non-artifact communication) | `peaks skill presence` heartbeat + transition `--reason` notes | `.peaks/.active-skill.json` (`lastHeartbeat`), transition notes in `.peaks/<sid>/<role>/requests/<rid>.md` | peaks does **not** separate Messages from Artifacts at the storage layer; a "message" is anything that is not the artefact body (the `<!-- peaks-memory:start -->` markers, the `state` field, the `--reason` text on a transition). Treat these as inline metadata of the artefact, not as separate objects. |
|
|
25
|
+
| **Part** (atomic content unit) | Markdown sections within an artefact, frontmatter fields | inline within the artefact | peaks's Artifacts are single Markdown files, so the "Part" concept maps to a heading or a frontmatter field. A `Part`'s `kind` in A2A terms is `text` (the prose), `file` (a `<!-- peaks-memory:start -->` block as a structured chunk), or `data` (the frontmatter). A2A's `form` / `iframe` / video `Part` kinds are not produced by peaks. |
|
|
26
|
+
|
|
27
|
+
## 3. Field-level mapping (A2A Part ↔ peaks frontmatter)
|
|
28
|
+
|
|
29
|
+
A2A `Part` has `kind` and `metadata` (free-form) plus `content` (typed by kind). peaks's per-artifact frontmatter carries a subset:
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
---
|
|
33
|
+
name: <slug> # used in memory extraction; not a 1:1 A2A field
|
|
34
|
+
description: <title> # roughly the A2A Artifact.description
|
|
35
|
+
metadata:
|
|
36
|
+
type: <kind> # A2A Artifact.kind equivalent
|
|
37
|
+
sourceArtifact: <rel> # A2A Artifact.source / provenance equivalent
|
|
38
|
+
---
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
A consumer reading a peaks artefact and translating it to A2A can populate:
|
|
42
|
+
|
|
43
|
+
- `Artifact.name` ← peaks `name`
|
|
44
|
+
- `Artifact.description` ← peaks `description` (or the first H1)
|
|
45
|
+
- `Artifact.kind` ← peaks `metadata.type`
|
|
46
|
+
- `Artifact.parts[0]` ← the body text (A2A `Part{kind: "text"}`)
|
|
47
|
+
- `Artifact.metadata.sourcePath` ← peaks `metadata.sourceArtifact`
|
|
48
|
+
- `Artifact.metadata.sessionId` ← from `.peaks/.session.json`
|
|
49
|
+
|
|
50
|
+
The mapping is not 100% lossless: A2A's `Part` can carry structured forms or file references, peaks cannot. That is the explicit *non-goal* of this mapping; it would be over-claiming to assert parity where there is none.
|
|
51
|
+
|
|
52
|
+
## 4. State-graph mapping (A2A Task ↔ peaks request)
|
|
53
|
+
|
|
54
|
+
A2A's Task object has a small set of states (typically: `submitted`, `working`, `input-required`, `completed`, `failed`, `canceled`). peaks's per-role request state machine is richer and per-role:
|
|
55
|
+
|
|
56
|
+
| Role | peaks states (in order) | Closest A2A Task state |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `prd` | `draft` → `confirmed-by-user` → `handed-off` | `submitted` → `working` → `input-required` (for the confirm gate) |
|
|
59
|
+
| `rd` | `draft` → `spec-locked` → `implemented` → `qa-handoff` | `working` |
|
|
60
|
+
| `qa` | `draft` → `running` → `verdict-issued` (verdict is `pass` / `return-to-rd` / `blocked`) | `working` → `completed` (pass) / `input-required` (return-to-rd) / `failed` (blocked) |
|
|
61
|
+
| `ui` | `draft` → `direction-locked` → `handed-off` | `working` → `completed` |
|
|
62
|
+
| `sc` | `draft` → `recorded` | `working` → `completed` |
|
|
63
|
+
|
|
64
|
+
A consumer translating peaks states to A2A should:
|
|
65
|
+
|
|
66
|
+
- collapse peaks's multi-role state machine to a *single* A2A Task state by taking the most progressed of any role,
|
|
67
|
+
- use the A2A `input-required` state to model **any** gate where peaks is waiting for a human (`confirmed-by-user`, `--confirm`, AskUserQuestion for a login wall, etc.),
|
|
68
|
+
- emit `completed` only when QA verdict is `pass` and SC has recorded the change,
|
|
69
|
+
- emit `failed` on `blocked` QA verdict or `blocked` handoff.
|
|
70
|
+
|
|
71
|
+
## 5. Worked example: a feature slice from start to finish
|
|
72
|
+
|
|
73
|
+
A user runs `peaks-solo` for a "add user authentication" feature. Mapping the resulting files to A2A concepts:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
.peaks/<sid>/prd/requests/001.md → A2A Artifact (kind=proposal)
|
|
77
|
+
.peaks/<sid>/ui/requests/001.md → A2A Artifact (kind=design-direction)
|
|
78
|
+
.peaks/<sid>/ui/design-draft.md → A2A Artifact (kind=visual-spec)
|
|
79
|
+
.peaks/<sid>/rd/tech-doc.md → A2A Artifact (kind=implementation-plan)
|
|
80
|
+
.peaks/<sid>/qa/test-cases/001.md → A2A Artifact (kind=test-cases)
|
|
81
|
+
.peaks/<sid>/rd/code-review.md → A2A Artifact (kind=review, status=fixed)
|
|
82
|
+
.peaks/<sid>/rd/security-review.md → A2A Artifact (kind=security-review)
|
|
83
|
+
.peaks/<sid>/qa/test-reports/001.md → A2A Artifact (kind=test-report, verdict=pass)
|
|
84
|
+
.peaks/<sid>/qa/security-findings.md → A2A Artifact (kind=security-findings)
|
|
85
|
+
.peaks/<sid>/qa/performance-findings.md → A2A Artifact (kind=performance-findings)
|
|
86
|
+
.peaks/<sid>/sc/handoff.md → A2A Artifact (kind=change-record)
|
|
87
|
+
.peaks/<sid>/txt/handoff.md → A2A Artifact (kind=handoff-capsule)
|
|
88
|
+
.peaks/<sid>/system/sub-agent-*.json → A2A Message (sub-agent presence markers)
|
|
89
|
+
.peaks/<sid>/sc/swarm-plan.json → A2A Message (the dispatch plan)
|
|
90
|
+
.peaks/memory/*.md → A2A Artifact (kind=project-memory, persists across sessions)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
A consumer wanting to render a single "feature" object in A2A terms picks the `test-reports/001.md` (verdict=pass) as the terminal Artifact and the rest as supporting Parts or sibling Artifacts. The mapping is intentionally loose: peaks's value is that *all of these files exist*, not that they fit A2A's object model exactly.
|
|
94
|
+
|
|
95
|
+
## 6. What peaks does NOT provide
|
|
96
|
+
|
|
97
|
+
To keep the mapping honest, peaks-cli **does not** currently provide the following A2A primitives, and consumers should not expect them:
|
|
98
|
+
|
|
99
|
+
- A2A **AgentCard** served over HTTP at `/.well-known/agent-card.json`. peaks-cli is a local CLI; its "card" is the on-disk `.peaks/.active-skill.json` plus `peaks skill doctor --json`.
|
|
100
|
+
- A2A **streaming** responses (SSE / WebSocket). peaks commands are synchronous and return a single JSON envelope.
|
|
101
|
+
- A2A **identity / auth** (OAuth, OIDC, mTLS). peaks assumes local-machine trust.
|
|
102
|
+
- A2A **cross-vendor discovery**. peaks has no A2A registry entry; it has `peaks mcp list --json` for MCP-compatible capabilities.
|
|
103
|
+
- A2A **Task delegation across the network**. peaks's "sub-agent" is a Claude Code `Task` tool call in the same process, not a remote A2A server.
|
|
104
|
+
|
|
105
|
+
These are *deliberate* omissions. peaks-cli solves a different problem (a local workflow-gating CLI for Claude Code), and adopting A2A's networking surface would add weight without addressing peaks's actual failure modes (which are around LLM bypassing gates, not around inter-agent discovery).
|
|
106
|
+
|
|
107
|
+
## 7. When to re-evaluate
|
|
108
|
+
|
|
109
|
+
Re-open this mapping in any of the following cases:
|
|
110
|
+
|
|
111
|
+
- a peaks user reports a real need to share workflow state with a non-peaks agent (e.g. an Autogen / LangChain agent that wants to read a peaks handoff capsule);
|
|
112
|
+
- peaks-cli ships a hosted / multi-user mode where AgentCard-style discovery becomes useful;
|
|
113
|
+
- the A2A protocol stabilises on a thin `Artifact` JSON schema that matches peaks's on-disk shape close enough to make translation a one-liner rather than a reference doc.
|
|
114
|
+
|
|
115
|
+
Until one of those fires, this reference doc is the entire A2A surface area of peaks-cli. Adding more is over-engineering.
|