@tenderprompt/cli 0.1.21 → 0.1.23
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/artifact-memory.d.ts +103 -0
- package/dist/artifact-memory.js +283 -0
- package/dist/index.js +700 -2
- package/package.json +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export declare const ARTIFACT_MEMORY_SCHEMA = "tender.artifact-memory.v1";
|
|
2
|
+
export declare const ARTIFACT_MEMORY_NOTES_REF = "refs/notes/tender/memory";
|
|
3
|
+
export declare const ARTIFACT_MEMORY_NOTES_SHORT_REF = "tender/memory";
|
|
4
|
+
export type ArtifactMemoryKind = 'intent' | 'observation' | 'decision' | 'change' | 'validation' | 'failure' | 'hypothesis' | 'open_question' | 'procedure' | 'support_handoff' | 'reflection' | 'summary' | 'note';
|
|
5
|
+
export type ArtifactMemoryEntry = {
|
|
6
|
+
schema: typeof ARTIFACT_MEMORY_SCHEMA;
|
|
7
|
+
entryId: string;
|
|
8
|
+
sessionId: string;
|
|
9
|
+
artifactId: string;
|
|
10
|
+
tenantId?: string;
|
|
11
|
+
commitSha: string;
|
|
12
|
+
kind: ArtifactMemoryKind;
|
|
13
|
+
originalKind?: string;
|
|
14
|
+
visibility: 'support';
|
|
15
|
+
summary: string;
|
|
16
|
+
body?: string;
|
|
17
|
+
tags: string[];
|
|
18
|
+
status?: 'open' | 'resolved' | 'superseded';
|
|
19
|
+
severity?: 'info' | 'warning' | 'error';
|
|
20
|
+
source: {
|
|
21
|
+
actor: 'agent' | 'user' | 'cli';
|
|
22
|
+
command?: string;
|
|
23
|
+
};
|
|
24
|
+
related?: {
|
|
25
|
+
files?: string[];
|
|
26
|
+
commands?: string[];
|
|
27
|
+
previewUrl?: string;
|
|
28
|
+
publishedUrl?: string;
|
|
29
|
+
releaseId?: string;
|
|
30
|
+
issueUrl?: string;
|
|
31
|
+
};
|
|
32
|
+
safety: {
|
|
33
|
+
redacted: boolean;
|
|
34
|
+
warnings: string[];
|
|
35
|
+
};
|
|
36
|
+
createdAt: string;
|
|
37
|
+
};
|
|
38
|
+
export type ArtifactMemoryCommandRunner = (command: string, args: string[], options: {
|
|
39
|
+
cwd: string;
|
|
40
|
+
}) => Promise<{
|
|
41
|
+
exitCode: number;
|
|
42
|
+
stdout: string;
|
|
43
|
+
stderr: string;
|
|
44
|
+
}>;
|
|
45
|
+
export type ArtifactMemoryAppendInput = {
|
|
46
|
+
repoPath: string;
|
|
47
|
+
artifactId?: string | null;
|
|
48
|
+
tenantId?: string | null;
|
|
49
|
+
commitSha?: string | null;
|
|
50
|
+
kind: string;
|
|
51
|
+
summary: string;
|
|
52
|
+
body?: string | null;
|
|
53
|
+
tags?: string[];
|
|
54
|
+
status?: ArtifactMemoryEntry['status'];
|
|
55
|
+
severity?: ArtifactMemoryEntry['severity'];
|
|
56
|
+
source?: ArtifactMemoryEntry['source'];
|
|
57
|
+
related?: ArtifactMemoryEntry['related'];
|
|
58
|
+
now?: () => number;
|
|
59
|
+
runner: ArtifactMemoryCommandRunner;
|
|
60
|
+
};
|
|
61
|
+
export type ArtifactMemoryAppendResult = {
|
|
62
|
+
entry: ArtifactMemoryEntry;
|
|
63
|
+
warnings: string[];
|
|
64
|
+
noteBytes: number;
|
|
65
|
+
};
|
|
66
|
+
export type ArtifactMemorySyncResult = {
|
|
67
|
+
status: 'fetched' | 'pushed' | 'not_configured' | 'failed';
|
|
68
|
+
ref: typeof ARTIFACT_MEMORY_NOTES_REF;
|
|
69
|
+
commitSha: string | null;
|
|
70
|
+
error?: string;
|
|
71
|
+
};
|
|
72
|
+
export declare function normalizeArtifactMemoryKind(kind: string): {
|
|
73
|
+
kind: ArtifactMemoryKind;
|
|
74
|
+
originalKind?: string;
|
|
75
|
+
warnings: string[];
|
|
76
|
+
tags: string[];
|
|
77
|
+
};
|
|
78
|
+
export declare function redactArtifactMemoryText(value: string): {
|
|
79
|
+
text: string;
|
|
80
|
+
redacted: boolean;
|
|
81
|
+
warnings: string[];
|
|
82
|
+
};
|
|
83
|
+
export declare function readArtifactMemoryHead(input: {
|
|
84
|
+
repoPath: string;
|
|
85
|
+
runner: ArtifactMemoryCommandRunner;
|
|
86
|
+
}): Promise<string>;
|
|
87
|
+
export declare function appendArtifactMemoryNote(input: ArtifactMemoryAppendInput): Promise<ArtifactMemoryAppendResult>;
|
|
88
|
+
export declare function readArtifactMemoryEntries(input: {
|
|
89
|
+
repoPath: string;
|
|
90
|
+
runner: ArtifactMemoryCommandRunner;
|
|
91
|
+
commitSha?: string | null;
|
|
92
|
+
}): Promise<ArtifactMemoryEntry[]>;
|
|
93
|
+
export declare function fetchArtifactMemoryNotes(input: {
|
|
94
|
+
repoPath: string;
|
|
95
|
+
remoteName: string;
|
|
96
|
+
runner: ArtifactMemoryCommandRunner;
|
|
97
|
+
}): Promise<ArtifactMemorySyncResult>;
|
|
98
|
+
export declare function pushArtifactMemoryNotes(input: {
|
|
99
|
+
repoPath: string;
|
|
100
|
+
remoteName: string;
|
|
101
|
+
runner: ArtifactMemoryCommandRunner;
|
|
102
|
+
}): Promise<ArtifactMemorySyncResult>;
|
|
103
|
+
export declare function formatArtifactMemoryEntry(entry: ArtifactMemoryEntry): string;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
export const ARTIFACT_MEMORY_SCHEMA = 'tender.artifact-memory.v1';
|
|
3
|
+
export const ARTIFACT_MEMORY_NOTES_REF = 'refs/notes/tender/memory';
|
|
4
|
+
export const ARTIFACT_MEMORY_NOTES_SHORT_REF = 'tender/memory';
|
|
5
|
+
const ARTIFACT_MEMORY_ENTRY_MAX_BYTES = 8 * 1024;
|
|
6
|
+
const ARTIFACT_MEMORY_NOTE_MAX_BYTES = 256 * 1024;
|
|
7
|
+
const ARTIFACT_MEMORY_SESSION_CONFIG_KEY = 'tender.memory.sessionId';
|
|
8
|
+
const ARTIFACT_MEMORY_KINDS = new Set([
|
|
9
|
+
'intent',
|
|
10
|
+
'observation',
|
|
11
|
+
'decision',
|
|
12
|
+
'change',
|
|
13
|
+
'validation',
|
|
14
|
+
'failure',
|
|
15
|
+
'hypothesis',
|
|
16
|
+
'open_question',
|
|
17
|
+
'procedure',
|
|
18
|
+
'support_handoff',
|
|
19
|
+
'reflection',
|
|
20
|
+
'summary',
|
|
21
|
+
'note',
|
|
22
|
+
]);
|
|
23
|
+
const KIND_ALIASES = new Map([
|
|
24
|
+
['thought', 'reflection'],
|
|
25
|
+
['debug_thought', 'reflection'],
|
|
26
|
+
['debug-thought', 'reflection'],
|
|
27
|
+
['error', 'failure'],
|
|
28
|
+
['issue', 'failure'],
|
|
29
|
+
['problem', 'failure'],
|
|
30
|
+
['question', 'open_question'],
|
|
31
|
+
['todo', 'open_question'],
|
|
32
|
+
['final', 'summary'],
|
|
33
|
+
]);
|
|
34
|
+
function byteLength(value) {
|
|
35
|
+
return new TextEncoder().encode(value).byteLength;
|
|
36
|
+
}
|
|
37
|
+
function normalizeTags(tags, fallback) {
|
|
38
|
+
const normalized = (tags ?? [])
|
|
39
|
+
.map((tag) => tag.trim().toLowerCase().replace(/[^a-z0-9_.:-]+/g, '-'))
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.slice(0, 12);
|
|
42
|
+
return normalized.length > 0 ? [...new Set(normalized)] : fallback;
|
|
43
|
+
}
|
|
44
|
+
export function normalizeArtifactMemoryKind(kind) {
|
|
45
|
+
const normalized = kind.trim().toLowerCase().replaceAll('-', '_');
|
|
46
|
+
if (ARTIFACT_MEMORY_KINDS.has(normalized)) {
|
|
47
|
+
return {
|
|
48
|
+
kind: normalized,
|
|
49
|
+
warnings: [],
|
|
50
|
+
tags: [],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const alias = KIND_ALIASES.get(normalized);
|
|
54
|
+
if (alias && ARTIFACT_MEMORY_KINDS.has(alias)) {
|
|
55
|
+
return {
|
|
56
|
+
kind: alias,
|
|
57
|
+
originalKind: kind,
|
|
58
|
+
warnings: ['unknown_kind_normalized'],
|
|
59
|
+
tags: [],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
kind: 'note',
|
|
64
|
+
originalKind: kind,
|
|
65
|
+
warnings: ['unknown_kind_normalized'],
|
|
66
|
+
tags: ['uncategorized'],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const SECRET_PATTERNS = [
|
|
70
|
+
[/\b(authorization|proxy-authorization)\s*[:=]\s*(bearer\s+)?[^\s,;'"`]+/gi, '$1: [REDACTED]'],
|
|
71
|
+
[/\b(cookie|set-cookie)\s*[:=]\s*[^\n\r]+/gi, '$1: [REDACTED]'],
|
|
72
|
+
[/\b(x-api-key|api-key|private-token|x-shopify-access-token)\s*[:=]\s*[^\s,;'"`]+/gi, '$1: [REDACTED]'],
|
|
73
|
+
[/\b([A-Z][A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|API_KEY|ACCESS_KEY|PRIVATE_KEY))=([^\s]+)/g, '$1=[REDACTED]'],
|
|
74
|
+
[/\b(tp_[A-Za-z0-9._-]{12,})\b/g, '[REDACTED_TOKEN]'],
|
|
75
|
+
[/\b(gh[pousr]_[A-Za-z0-9_]{20,})\b/g, '[REDACTED_TOKEN]'],
|
|
76
|
+
[/\b(sk-[A-Za-z0-9_-]{20,})\b/g, '[REDACTED_TOKEN]'],
|
|
77
|
+
];
|
|
78
|
+
export function redactArtifactMemoryText(value) {
|
|
79
|
+
let text = value;
|
|
80
|
+
const warnings = new Set();
|
|
81
|
+
for (const [pattern, replacement] of SECRET_PATTERNS) {
|
|
82
|
+
const next = text.replace(pattern, replacement);
|
|
83
|
+
if (next !== text) {
|
|
84
|
+
warnings.add('secret_like_value_redacted');
|
|
85
|
+
text = next;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
text,
|
|
90
|
+
redacted: warnings.size > 0,
|
|
91
|
+
warnings: [...warnings],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function assertUsefulMemoryText(input) {
|
|
95
|
+
if (!input.summary.trim() && !input.body?.trim()) {
|
|
96
|
+
throw new Error('Memory entries require --summary or --body.\nNext: tender memory add --summary "What future agents should know"');
|
|
97
|
+
}
|
|
98
|
+
const total = `${input.summary}\n${input.body ?? ''}`.trim();
|
|
99
|
+
const lineCount = total.split(/\r?\n/).length;
|
|
100
|
+
if (lineCount > 240 || byteLength(total) > ARTIFACT_MEMORY_ENTRY_MAX_BYTES) {
|
|
101
|
+
throw new Error(`Memory entries are limited to ${ARTIFACT_MEMORY_ENTRY_MAX_BYTES} bytes. Summarize the relevant context instead.\nNext: tender memory handoff --reason "Need Tender support" --summary "Current blocker and what was tried"`);
|
|
102
|
+
}
|
|
103
|
+
if (lineCount > 40 && !/^summary|support_handoff|reflection$/i.test(input.summary)) {
|
|
104
|
+
throw new Error('Memory entries should be bounded summaries, not raw transcript dumps.\nNext: tender memory add --kind summary --summary "Compact the useful context" --body "Goal, changes, failures, and next step"');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function runGit(input) {
|
|
108
|
+
const result = await input.runner('git', input.args, { cwd: input.repoPath });
|
|
109
|
+
if (!input.allowFailure && result.exitCode !== 0) {
|
|
110
|
+
throw new Error([`git ${input.args.join(' ')} failed with exit code ${result.exitCode}.`, result.stderr.trim() || result.stdout.trim()]
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
.join('\n'));
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
export async function readArtifactMemoryHead(input) {
|
|
117
|
+
const result = await runGit({
|
|
118
|
+
...input,
|
|
119
|
+
args: ['rev-parse', 'HEAD'],
|
|
120
|
+
});
|
|
121
|
+
const commitSha = result.stdout.trim();
|
|
122
|
+
if (!/^[0-9a-f]{40}$/i.test(commitSha)) {
|
|
123
|
+
throw new Error('Artifact memory requires a checkout with a valid HEAD commit.\nNext: commit your source or run tender app init <app-id> --dir .');
|
|
124
|
+
}
|
|
125
|
+
return commitSha;
|
|
126
|
+
}
|
|
127
|
+
async function readArtifactMemorySessionId(input) {
|
|
128
|
+
const existing = await runGit({
|
|
129
|
+
...input,
|
|
130
|
+
args: ['config', '--local', '--get', ARTIFACT_MEMORY_SESSION_CONFIG_KEY],
|
|
131
|
+
allowFailure: true,
|
|
132
|
+
});
|
|
133
|
+
const existingSessionId = existing.exitCode === 0 ? existing.stdout.trim() : '';
|
|
134
|
+
if (existingSessionId) {
|
|
135
|
+
return existingSessionId;
|
|
136
|
+
}
|
|
137
|
+
const sessionId = `sess_${randomUUID()}`;
|
|
138
|
+
await runGit({
|
|
139
|
+
...input,
|
|
140
|
+
args: ['config', '--local', ARTIFACT_MEMORY_SESSION_CONFIG_KEY, sessionId],
|
|
141
|
+
});
|
|
142
|
+
return sessionId;
|
|
143
|
+
}
|
|
144
|
+
async function inferArtifactIdFromRemote(input) {
|
|
145
|
+
const result = await runGit({
|
|
146
|
+
...input,
|
|
147
|
+
args: ['remote', '-v'],
|
|
148
|
+
allowFailure: true,
|
|
149
|
+
});
|
|
150
|
+
if (result.exitCode !== 0) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const match = result.stdout.match(/\bartifact_[A-Za-z0-9_-]+(?=\.git|\b|\/)/);
|
|
154
|
+
return match?.[0] ?? null;
|
|
155
|
+
}
|
|
156
|
+
async function readExistingNote(input) {
|
|
157
|
+
const result = await runGit({
|
|
158
|
+
...input,
|
|
159
|
+
args: ['notes', `--ref=${ARTIFACT_MEMORY_NOTES_SHORT_REF}`, 'show', input.commitSha],
|
|
160
|
+
allowFailure: true,
|
|
161
|
+
});
|
|
162
|
+
return result.exitCode === 0 ? result.stdout : '';
|
|
163
|
+
}
|
|
164
|
+
export async function appendArtifactMemoryNote(input) {
|
|
165
|
+
assertUsefulMemoryText(input);
|
|
166
|
+
const commitSha = input.commitSha ?? (await readArtifactMemoryHead(input));
|
|
167
|
+
const artifactId = input.artifactId ?? (await inferArtifactIdFromRemote(input));
|
|
168
|
+
if (!artifactId) {
|
|
169
|
+
throw new Error('Artifact memory requires an artifact id. Pass --artifact or run inside a checkout with a Tender artifact remote.\nNext: tender app git setup <app-id> --dir .');
|
|
170
|
+
}
|
|
171
|
+
const sessionId = await readArtifactMemorySessionId(input);
|
|
172
|
+
const normalizedKind = normalizeArtifactMemoryKind(input.kind);
|
|
173
|
+
const redactedSummary = redactArtifactMemoryText(input.summary.trim());
|
|
174
|
+
const redactedBody = input.body?.trim() ? redactArtifactMemoryText(input.body.trim()) : null;
|
|
175
|
+
const warnings = [
|
|
176
|
+
...normalizedKind.warnings,
|
|
177
|
+
...redactedSummary.warnings,
|
|
178
|
+
...(redactedBody?.warnings ?? []),
|
|
179
|
+
];
|
|
180
|
+
const entry = {
|
|
181
|
+
schema: ARTIFACT_MEMORY_SCHEMA,
|
|
182
|
+
entryId: `mem_${randomUUID()}`,
|
|
183
|
+
sessionId,
|
|
184
|
+
artifactId,
|
|
185
|
+
...(input.tenantId ? { tenantId: input.tenantId } : {}),
|
|
186
|
+
commitSha,
|
|
187
|
+
kind: normalizedKind.kind,
|
|
188
|
+
...(normalizedKind.originalKind ? { originalKind: normalizedKind.originalKind } : {}),
|
|
189
|
+
visibility: 'support',
|
|
190
|
+
summary: redactedSummary.text,
|
|
191
|
+
...(redactedBody?.text ? { body: redactedBody.text } : {}),
|
|
192
|
+
tags: normalizeTags(input.tags, normalizedKind.tags),
|
|
193
|
+
...(input.status ? { status: input.status } : {}),
|
|
194
|
+
...(input.severity ? { severity: input.severity } : {}),
|
|
195
|
+
source: input.source ?? { actor: 'agent' },
|
|
196
|
+
...(input.related && Object.keys(input.related).length > 0 ? { related: input.related } : {}),
|
|
197
|
+
safety: {
|
|
198
|
+
redacted: redactedSummary.redacted || Boolean(redactedBody?.redacted),
|
|
199
|
+
warnings: [...new Set(warnings)],
|
|
200
|
+
},
|
|
201
|
+
createdAt: new Date(input.now?.() ?? Date.now()).toISOString(),
|
|
202
|
+
};
|
|
203
|
+
const line = JSON.stringify(entry);
|
|
204
|
+
if (byteLength(line) > ARTIFACT_MEMORY_ENTRY_MAX_BYTES) {
|
|
205
|
+
throw new Error(`Memory entry is limited to ${ARTIFACT_MEMORY_ENTRY_MAX_BYTES} bytes after redaction.\nNext: tender memory add --kind summary --summary "Short context" --body "Bounded details only"`);
|
|
206
|
+
}
|
|
207
|
+
const existingNote = await readExistingNote({ ...input, commitSha });
|
|
208
|
+
const nextNote = `${existingNote.trimEnd()}${existingNote.trim() ? '\n' : ''}${line}\n`;
|
|
209
|
+
const noteBytes = byteLength(nextNote);
|
|
210
|
+
const noteWarnings = noteBytes > ARTIFACT_MEMORY_NOTE_MAX_BYTES ? ['commit_note_payload_large_compaction_recommended'] : [];
|
|
211
|
+
await runGit({
|
|
212
|
+
...input,
|
|
213
|
+
args: ['notes', `--ref=${ARTIFACT_MEMORY_NOTES_SHORT_REF}`, 'add', '-f', '-m', nextNote, commitSha],
|
|
214
|
+
});
|
|
215
|
+
return {
|
|
216
|
+
entry: noteWarnings.length > 0 ? { ...entry, safety: { ...entry.safety, warnings: [...entry.safety.warnings, ...noteWarnings] } } : entry,
|
|
217
|
+
warnings: [...new Set([...warnings, ...noteWarnings])],
|
|
218
|
+
noteBytes,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
export async function readArtifactMemoryEntries(input) {
|
|
222
|
+
const commitSha = input.commitSha ?? (await readArtifactMemoryHead(input));
|
|
223
|
+
const note = await readExistingNote({ ...input, commitSha });
|
|
224
|
+
return note
|
|
225
|
+
.split(/\r?\n/)
|
|
226
|
+
.map((line) => line.trim())
|
|
227
|
+
.filter(Boolean)
|
|
228
|
+
.flatMap((line) => {
|
|
229
|
+
try {
|
|
230
|
+
const value = JSON.parse(line);
|
|
231
|
+
return value?.schema === ARTIFACT_MEMORY_SCHEMA ? [value] : [];
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
export async function fetchArtifactMemoryNotes(input) {
|
|
239
|
+
const result = await runGit({
|
|
240
|
+
...input,
|
|
241
|
+
args: ['fetch', input.remoteName, `${ARTIFACT_MEMORY_NOTES_REF}:${ARTIFACT_MEMORY_NOTES_REF}`],
|
|
242
|
+
allowFailure: true,
|
|
243
|
+
});
|
|
244
|
+
if (result.exitCode !== 0) {
|
|
245
|
+
return {
|
|
246
|
+
status: 'failed',
|
|
247
|
+
ref: ARTIFACT_MEMORY_NOTES_REF,
|
|
248
|
+
commitSha: null,
|
|
249
|
+
error: result.stderr.trim() || result.stdout.trim() || 'git fetch notes failed',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
return { status: 'fetched', ref: ARTIFACT_MEMORY_NOTES_REF, commitSha: null };
|
|
253
|
+
}
|
|
254
|
+
export async function pushArtifactMemoryNotes(input) {
|
|
255
|
+
const head = await readArtifactMemoryHead(input).catch(() => null);
|
|
256
|
+
const remote = await runGit({
|
|
257
|
+
...input,
|
|
258
|
+
args: ['remote', 'get-url', input.remoteName],
|
|
259
|
+
allowFailure: true,
|
|
260
|
+
});
|
|
261
|
+
if (remote.exitCode !== 0) {
|
|
262
|
+
return { status: 'not_configured', ref: ARTIFACT_MEMORY_NOTES_REF, commitSha: head };
|
|
263
|
+
}
|
|
264
|
+
const result = await runGit({
|
|
265
|
+
...input,
|
|
266
|
+
args: ['push', input.remoteName, `${ARTIFACT_MEMORY_NOTES_REF}:${ARTIFACT_MEMORY_NOTES_REF}`],
|
|
267
|
+
allowFailure: true,
|
|
268
|
+
});
|
|
269
|
+
if (result.exitCode !== 0) {
|
|
270
|
+
return {
|
|
271
|
+
status: 'failed',
|
|
272
|
+
ref: ARTIFACT_MEMORY_NOTES_REF,
|
|
273
|
+
commitSha: head,
|
|
274
|
+
error: result.stderr.trim() || result.stdout.trim() || 'git push notes failed',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return { status: 'pushed', ref: ARTIFACT_MEMORY_NOTES_REF, commitSha: head };
|
|
278
|
+
}
|
|
279
|
+
export function formatArtifactMemoryEntry(entry) {
|
|
280
|
+
const tags = entry.tags.length > 0 ? ` [${entry.tags.join(', ')}]` : '';
|
|
281
|
+
const status = entry.status ? ` ${entry.status}` : '';
|
|
282
|
+
return `${entry.createdAt} ${entry.kind}${status}: ${entry.summary}${tags}`;
|
|
283
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6,12 +6,13 @@ import os from 'node:os';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { generateTenderAppEnvTypes, isForbiddenTenderAppRepoPath, isForbiddenTenderAppSecretPath, validateTenderApp, } from '@tenderprompt/tender-app-validator';
|
|
9
|
+
import { ARTIFACT_MEMORY_NOTES_REF, appendArtifactMemoryNote, fetchArtifactMemoryNotes, formatArtifactMemoryEntry, pushArtifactMemoryNotes, readArtifactMemoryEntries, readArtifactMemoryHead, } from "./artifact-memory.js";
|
|
9
10
|
import { collectTenderAppFiles } from "./local-files.js";
|
|
10
11
|
const CLI_PACKAGE_NAME = '@tenderprompt/cli';
|
|
11
12
|
const UNKNOWN_CLI_VERSION = '0.0.0';
|
|
12
13
|
const RECOMMENDED_TENDER_PROMPT_SKILL = {
|
|
13
14
|
name: 'tender-prompt',
|
|
14
|
-
version: '0.1.
|
|
15
|
+
version: '0.1.5',
|
|
15
16
|
source: 'git@github.com:tenderprompt/skills.git',
|
|
16
17
|
path: 'skills/tender-prompt/SKILL.md',
|
|
17
18
|
updateHint: 'Refresh the tender-prompt skill from git@github.com:tenderprompt/skills.git when the local skill version is older.',
|
|
@@ -79,6 +80,10 @@ Commands:
|
|
|
79
80
|
tender auth login --device
|
|
80
81
|
Authorize this machine with a browser-approved device flow
|
|
81
82
|
tender auth status Show whether a Tender API token is available
|
|
83
|
+
tender memory add Add artifact memory to Git notes for future agents and support
|
|
84
|
+
tender memory handoff Capture a support handoff memory entry
|
|
85
|
+
tender memory brief Print the latest useful artifact memory
|
|
86
|
+
tender memory flush Push artifact memory notes to the Tender Git remote
|
|
82
87
|
tender playbooks list List remote Tender Prompt playbook metadata
|
|
83
88
|
tender playbooks get <id> Fetch one remote playbook body on demand
|
|
84
89
|
tender playbooks file <id> --path <path>
|
|
@@ -143,6 +148,10 @@ Examples:
|
|
|
143
148
|
tender capabilities --json
|
|
144
149
|
tender auth login --device --profile edit-preview --artifact artifact_123 --ttl 7d
|
|
145
150
|
tender auth status --json
|
|
151
|
+
tender memory add --kind decision --summary "Kept auth in the host gateway" --json
|
|
152
|
+
tender memory handoff --reason "Preview publish is blocked" --summary "Need Tender support" --json
|
|
153
|
+
tender memory brief --json
|
|
154
|
+
tender memory flush --json
|
|
146
155
|
tender playbooks list --json
|
|
147
156
|
tender playbooks get recharge-bundle-builder --json
|
|
148
157
|
tender playbooks file recharge-bundle-builder --path references/recharge-payloads.md --json
|
|
@@ -219,6 +228,52 @@ Examples:
|
|
|
219
228
|
tender auth status
|
|
220
229
|
tender auth status --profile publish --json`;
|
|
221
230
|
}
|
|
231
|
+
function memoryHelp() {
|
|
232
|
+
return `Usage: tender memory <command> [options]
|
|
233
|
+
|
|
234
|
+
Commands:
|
|
235
|
+
tender memory start --summary <text>
|
|
236
|
+
Start an artifact memory session. Optional; sessions are created lazily.
|
|
237
|
+
tender memory add --kind <kind> --summary <text>
|
|
238
|
+
Add one bounded artifact memory entry to Git notes.
|
|
239
|
+
tender memory handoff Capture a support-focused handoff entry.
|
|
240
|
+
tender memory show Show memory entries attached to the current commit.
|
|
241
|
+
tender memory brief Show the latest useful memory summary.
|
|
242
|
+
tender memory flush Push refs/notes/tender/memory to the configured remote.
|
|
243
|
+
tender memory export Export artifact memory entries as JSONL.
|
|
244
|
+
|
|
245
|
+
Options:
|
|
246
|
+
--dir <path> Artifact checkout directory. Defaults to current directory.
|
|
247
|
+
--artifact <id> Artifact id when it cannot be inferred from the remote.
|
|
248
|
+
--remote <name> Git remote to fetch or push notes. Defaults to tender.
|
|
249
|
+
--json Emit stable JSON for coding agents.
|
|
250
|
+
|
|
251
|
+
Examples:
|
|
252
|
+
tender memory add --kind intent --summary "Fix checkout submit flow" --json
|
|
253
|
+
tender memory add --kind decision --summary "Kept credential injection in the host gateway" --body "Generated code should not set Authorization headers." --json
|
|
254
|
+
tender memory handoff --reason "Need Tender support debugging publish failure" --summary "Preview works but publish fails" --json
|
|
255
|
+
tender memory brief --json
|
|
256
|
+
tender memory export --format jsonl
|
|
257
|
+
tender memory flush --json`;
|
|
258
|
+
}
|
|
259
|
+
function memoryAddHelp() {
|
|
260
|
+
return `Usage: tender memory add --kind <kind> --summary <text> [--body <text>] [--tag <tag>] [--dir <path>] [--artifact <id>] [--remote <name>] [--json]
|
|
261
|
+
|
|
262
|
+
Known kinds: intent, observation, decision, change, validation, failure, hypothesis, open_question, procedure, support_handoff, reflection, summary, note.
|
|
263
|
+
Unknown kinds are stored as kind "note" with originalKind preserved.
|
|
264
|
+
|
|
265
|
+
Examples:
|
|
266
|
+
tender memory add --kind observation --summary "The app reads product.metadata.heroVideoUrl" --json
|
|
267
|
+
tender memory add --kind summary --summary "Session summary" --body "Changed src/server.ts, validated, and previewed." --json`;
|
|
268
|
+
}
|
|
269
|
+
function memoryHandoffHelp() {
|
|
270
|
+
return `Usage: tender memory handoff [--reason <text>] [--summary <text>] [--body <text>] [--dir <path>] [--artifact <id>] [--remote <name>] [--json]
|
|
271
|
+
|
|
272
|
+
Use this when the user wants to ask Tender for help. If --reason is omitted, the CLI tries to infer context from recent failure or open_question entries.
|
|
273
|
+
|
|
274
|
+
Examples:
|
|
275
|
+
tender memory handoff --reason "Need Tender support debugging preview failure" --summary "Preview route /api/vote returns 500" --json`;
|
|
276
|
+
}
|
|
222
277
|
function playbooksHelp() {
|
|
223
278
|
return `Usage: tender playbooks <command> [options]
|
|
224
279
|
|
|
@@ -1087,6 +1142,7 @@ function tenderCliCapabilities() {
|
|
|
1087
1142
|
'tender --version',
|
|
1088
1143
|
'tender --help',
|
|
1089
1144
|
'tender auth --help',
|
|
1145
|
+
'tender memory --help',
|
|
1090
1146
|
'tender playbooks --help',
|
|
1091
1147
|
'tender app --help',
|
|
1092
1148
|
'tender app agent heartbeat --help',
|
|
@@ -1163,6 +1219,7 @@ function tenderCliCapabilities() {
|
|
|
1163
1219
|
when: 'When editing app source from a local checkout.',
|
|
1164
1220
|
commands: [
|
|
1165
1221
|
'tender app agent heartbeat <artifact-id> --source codex --status connected --summary "Loaded Tender Prompt context" --json',
|
|
1222
|
+
'tender memory add --artifact <artifact-id> --kind intent --summary "User goal and current plan" --json',
|
|
1166
1223
|
'tender app doctor --dir <dir> --json',
|
|
1167
1224
|
'tender app agent heartbeat <artifact-id> --source codex --status working --phase editing --summary "Editing app files" --json',
|
|
1168
1225
|
'tender app git setup <artifact-id> --dir <dir> --dry-run --json',
|
|
@@ -1172,9 +1229,13 @@ function tenderCliCapabilities() {
|
|
|
1172
1229
|
'tender app outbound-auth list <artifact-id> --json',
|
|
1173
1230
|
'tender app publish <artifact-id> --dry-run --json',
|
|
1174
1231
|
'tender app publish <artifact-id> --commit $(git rev-parse HEAD) --confirm publish --stream --json',
|
|
1232
|
+
'tender memory add --kind summary --summary "What changed, what passed, and what remains" --json',
|
|
1233
|
+
'tender memory flush --json',
|
|
1175
1234
|
],
|
|
1176
1235
|
notes: [
|
|
1177
1236
|
'Send heartbeat updates only at meaningful milestones or when waiting for user input; do not send rapid polling updates.',
|
|
1237
|
+
'Use tender memory add for durable artifact context that future agents or Tender support should know.',
|
|
1238
|
+
'Use tender memory handoff when blocked or when the user asks to contact Tender support.',
|
|
1178
1239
|
'If preview or publish reports outbound auth grant approval required, run tender app outbound-auth approve <artifact-id> --all --confirm approve --json, then retry.',
|
|
1179
1240
|
'Publishing is production-facing and requires explicit user intent.',
|
|
1180
1241
|
],
|
|
@@ -3151,6 +3212,201 @@ function parseContextRefreshArgs(args) {
|
|
|
3151
3212
|
json,
|
|
3152
3213
|
};
|
|
3153
3214
|
}
|
|
3215
|
+
function parseMemoryCommonArgs(args) {
|
|
3216
|
+
let dir = '.';
|
|
3217
|
+
let artifactId = null;
|
|
3218
|
+
let remoteName = 'tender';
|
|
3219
|
+
let json = false;
|
|
3220
|
+
const remaining = [];
|
|
3221
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
3222
|
+
const arg = args[index];
|
|
3223
|
+
if (arg === '--json') {
|
|
3224
|
+
json = true;
|
|
3225
|
+
continue;
|
|
3226
|
+
}
|
|
3227
|
+
if (arg === '--dir') {
|
|
3228
|
+
dir = parseRequiredFlagValue(args[index + 1], '--dir');
|
|
3229
|
+
index += 1;
|
|
3230
|
+
continue;
|
|
3231
|
+
}
|
|
3232
|
+
if (arg === '--artifact') {
|
|
3233
|
+
artifactId = parseRequiredFlagValue(args[index + 1], '--artifact');
|
|
3234
|
+
index += 1;
|
|
3235
|
+
continue;
|
|
3236
|
+
}
|
|
3237
|
+
if (arg === '--remote') {
|
|
3238
|
+
remoteName = parseRemoteName(args[index + 1]);
|
|
3239
|
+
index += 1;
|
|
3240
|
+
continue;
|
|
3241
|
+
}
|
|
3242
|
+
remaining.push(arg);
|
|
3243
|
+
}
|
|
3244
|
+
return { dir, artifactId, remoteName, json, remaining };
|
|
3245
|
+
}
|
|
3246
|
+
function parseMemoryStartArgs(args) {
|
|
3247
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
3248
|
+
return { command: 'help', topic: 'memory start' };
|
|
3249
|
+
}
|
|
3250
|
+
const parsed = parseMemoryCommonArgs(args);
|
|
3251
|
+
let summary = '';
|
|
3252
|
+
let body = null;
|
|
3253
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3254
|
+
const arg = parsed.remaining[index];
|
|
3255
|
+
if (arg === '--summary') {
|
|
3256
|
+
summary = parseRequiredFlagValue(parsed.remaining[index + 1], '--summary');
|
|
3257
|
+
index += 1;
|
|
3258
|
+
continue;
|
|
3259
|
+
}
|
|
3260
|
+
if (arg === '--body') {
|
|
3261
|
+
body = parseRequiredFlagValue(parsed.remaining[index + 1], '--body');
|
|
3262
|
+
index += 1;
|
|
3263
|
+
continue;
|
|
3264
|
+
}
|
|
3265
|
+
throw new TenderCliUsageError(`Unknown memory start option: ${arg}`);
|
|
3266
|
+
}
|
|
3267
|
+
if (!summary.trim()) {
|
|
3268
|
+
throw new TenderCliUsageError('memory start requires --summary.\nNext: tender memory start --summary "Current user goal"');
|
|
3269
|
+
}
|
|
3270
|
+
return { command: 'memory start', summary, body, ...parsed };
|
|
3271
|
+
}
|
|
3272
|
+
function parseMemoryAddArgs(args) {
|
|
3273
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
3274
|
+
return { command: 'help', topic: 'memory add' };
|
|
3275
|
+
}
|
|
3276
|
+
const parsed = parseMemoryCommonArgs(args);
|
|
3277
|
+
let kind = 'note';
|
|
3278
|
+
let summary = '';
|
|
3279
|
+
let body = null;
|
|
3280
|
+
const tags = [];
|
|
3281
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3282
|
+
const arg = parsed.remaining[index];
|
|
3283
|
+
if (arg === '--kind') {
|
|
3284
|
+
kind = parseRequiredFlagValue(parsed.remaining[index + 1], '--kind');
|
|
3285
|
+
index += 1;
|
|
3286
|
+
continue;
|
|
3287
|
+
}
|
|
3288
|
+
if (arg === '--summary') {
|
|
3289
|
+
summary = parseRequiredFlagValue(parsed.remaining[index + 1], '--summary');
|
|
3290
|
+
index += 1;
|
|
3291
|
+
continue;
|
|
3292
|
+
}
|
|
3293
|
+
if (arg === '--body') {
|
|
3294
|
+
body = parseRequiredFlagValue(parsed.remaining[index + 1], '--body');
|
|
3295
|
+
index += 1;
|
|
3296
|
+
continue;
|
|
3297
|
+
}
|
|
3298
|
+
if (arg === '--tag') {
|
|
3299
|
+
tags.push(parseRequiredFlagValue(parsed.remaining[index + 1], '--tag'));
|
|
3300
|
+
index += 1;
|
|
3301
|
+
continue;
|
|
3302
|
+
}
|
|
3303
|
+
throw new TenderCliUsageError(`Unknown memory add option: ${arg}`);
|
|
3304
|
+
}
|
|
3305
|
+
return { command: 'memory add', kind, summary, body, tags, ...parsed };
|
|
3306
|
+
}
|
|
3307
|
+
function parseMemoryHandoffArgs(args) {
|
|
3308
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
3309
|
+
return { command: 'help', topic: 'memory handoff' };
|
|
3310
|
+
}
|
|
3311
|
+
const parsed = parseMemoryCommonArgs(args);
|
|
3312
|
+
let reason = null;
|
|
3313
|
+
let summary = null;
|
|
3314
|
+
let body = null;
|
|
3315
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3316
|
+
const arg = parsed.remaining[index];
|
|
3317
|
+
if (arg === '--reason') {
|
|
3318
|
+
reason = parseRequiredFlagValue(parsed.remaining[index + 1], '--reason');
|
|
3319
|
+
index += 1;
|
|
3320
|
+
continue;
|
|
3321
|
+
}
|
|
3322
|
+
if (arg === '--summary') {
|
|
3323
|
+
summary = parseRequiredFlagValue(parsed.remaining[index + 1], '--summary');
|
|
3324
|
+
index += 1;
|
|
3325
|
+
continue;
|
|
3326
|
+
}
|
|
3327
|
+
if (arg === '--body') {
|
|
3328
|
+
body = parseRequiredFlagValue(parsed.remaining[index + 1], '--body');
|
|
3329
|
+
index += 1;
|
|
3330
|
+
continue;
|
|
3331
|
+
}
|
|
3332
|
+
throw new TenderCliUsageError(`Unknown memory handoff option: ${arg}`);
|
|
3333
|
+
}
|
|
3334
|
+
return { command: 'memory handoff', reason, summary, body, ...parsed };
|
|
3335
|
+
}
|
|
3336
|
+
function parseMemoryShowArgs(args) {
|
|
3337
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
3338
|
+
return { command: 'help', topic: 'memory show' };
|
|
3339
|
+
}
|
|
3340
|
+
const parsed = parseMemoryCommonArgs(args);
|
|
3341
|
+
let commitSha = null;
|
|
3342
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3343
|
+
const arg = parsed.remaining[index];
|
|
3344
|
+
if (arg === '--commit') {
|
|
3345
|
+
commitSha = parseRequiredFlagValue(parsed.remaining[index + 1], '--commit');
|
|
3346
|
+
index += 1;
|
|
3347
|
+
continue;
|
|
3348
|
+
}
|
|
3349
|
+
throw new TenderCliUsageError(`Unknown memory show option: ${arg}`);
|
|
3350
|
+
}
|
|
3351
|
+
return { command: 'memory show', commitSha, ...parsed };
|
|
3352
|
+
}
|
|
3353
|
+
function parseMemoryBriefArgs(args) {
|
|
3354
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
3355
|
+
return { command: 'help', topic: 'memory brief' };
|
|
3356
|
+
}
|
|
3357
|
+
const parsed = parseMemoryCommonArgs(args);
|
|
3358
|
+
let limit = 12;
|
|
3359
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3360
|
+
const arg = parsed.remaining[index];
|
|
3361
|
+
if (arg === '--limit') {
|
|
3362
|
+
limit = parsePositiveIntegerFlag(parsed.remaining[index + 1], '--limit');
|
|
3363
|
+
index += 1;
|
|
3364
|
+
continue;
|
|
3365
|
+
}
|
|
3366
|
+
throw new TenderCliUsageError(`Unknown memory brief option: ${arg}`);
|
|
3367
|
+
}
|
|
3368
|
+
return { command: 'memory brief', limit, ...parsed };
|
|
3369
|
+
}
|
|
3370
|
+
function parseMemoryFlushArgs(args) {
|
|
3371
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
3372
|
+
return { command: 'help', topic: 'memory flush' };
|
|
3373
|
+
}
|
|
3374
|
+
const parsed = parseMemoryCommonArgs(args);
|
|
3375
|
+
if (parsed.remaining.length > 0) {
|
|
3376
|
+
throw new TenderCliUsageError(`Unknown memory flush option: ${parsed.remaining[0]}`);
|
|
3377
|
+
}
|
|
3378
|
+
return { command: 'memory flush', ...parsed };
|
|
3379
|
+
}
|
|
3380
|
+
function parseMemoryExportArgs(args) {
|
|
3381
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
3382
|
+
return { command: 'help', topic: 'memory export' };
|
|
3383
|
+
}
|
|
3384
|
+
const parsed = parseMemoryCommonArgs(args);
|
|
3385
|
+
let format = null;
|
|
3386
|
+
let commitSha = null;
|
|
3387
|
+
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
3388
|
+
const arg = parsed.remaining[index];
|
|
3389
|
+
if (arg === '--format') {
|
|
3390
|
+
const value = parseRequiredFlagValue(parsed.remaining[index + 1], '--format');
|
|
3391
|
+
if (value !== 'jsonl') {
|
|
3392
|
+
throw new TenderCliUsageError(`Unsupported memory export format: ${value}.\nNext: tender memory export --format jsonl --dir ${parsed.dir}`);
|
|
3393
|
+
}
|
|
3394
|
+
format = value;
|
|
3395
|
+
index += 1;
|
|
3396
|
+
continue;
|
|
3397
|
+
}
|
|
3398
|
+
if (arg === '--commit') {
|
|
3399
|
+
commitSha = parseRequiredFlagValue(parsed.remaining[index + 1], '--commit');
|
|
3400
|
+
index += 1;
|
|
3401
|
+
continue;
|
|
3402
|
+
}
|
|
3403
|
+
throw new TenderCliUsageError(`Unknown memory export option: ${arg}`);
|
|
3404
|
+
}
|
|
3405
|
+
if (format === null) {
|
|
3406
|
+
throw new TenderCliUsageError(`memory export requires --format jsonl.\nNext: tender memory export --format jsonl --dir ${parsed.dir}`);
|
|
3407
|
+
}
|
|
3408
|
+
return { command: 'memory export', format, commitSha, ...parsed };
|
|
3409
|
+
}
|
|
3154
3410
|
function parseTokenProfile(value) {
|
|
3155
3411
|
if (value === 'edit-preview' || value === 'publish' || value === 'create' || value === 'db-read') {
|
|
3156
3412
|
return value;
|
|
@@ -4848,6 +5104,33 @@ function parseTenderArgs(args) {
|
|
|
4848
5104
|
}
|
|
4849
5105
|
throw new TenderCliUsageError(`Unknown auth command: ${args[1]}`);
|
|
4850
5106
|
}
|
|
5107
|
+
if (args[0] === 'memory') {
|
|
5108
|
+
if (args[1] === undefined || args[1] === '--help' || args[1] === '-h') {
|
|
5109
|
+
return { command: 'help', topic: 'memory' };
|
|
5110
|
+
}
|
|
5111
|
+
if (args[1] === 'start') {
|
|
5112
|
+
return parseMemoryStartArgs(args.slice(2));
|
|
5113
|
+
}
|
|
5114
|
+
if (args[1] === 'add') {
|
|
5115
|
+
return parseMemoryAddArgs(args.slice(2));
|
|
5116
|
+
}
|
|
5117
|
+
if (args[1] === 'handoff') {
|
|
5118
|
+
return parseMemoryHandoffArgs(args.slice(2));
|
|
5119
|
+
}
|
|
5120
|
+
if (args[1] === 'show') {
|
|
5121
|
+
return parseMemoryShowArgs(args.slice(2));
|
|
5122
|
+
}
|
|
5123
|
+
if (args[1] === 'brief') {
|
|
5124
|
+
return parseMemoryBriefArgs(args.slice(2));
|
|
5125
|
+
}
|
|
5126
|
+
if (args[1] === 'flush') {
|
|
5127
|
+
return parseMemoryFlushArgs(args.slice(2));
|
|
5128
|
+
}
|
|
5129
|
+
if (args[1] === 'export') {
|
|
5130
|
+
return parseMemoryExportArgs(args.slice(2));
|
|
5131
|
+
}
|
|
5132
|
+
throw new TenderCliUsageError(`Unknown memory command: ${args[1]}`);
|
|
5133
|
+
}
|
|
4851
5134
|
if (args[0] === 'playbooks') {
|
|
4852
5135
|
if (args[1] === undefined || args[1] === '--help' || args[1] === '-h') {
|
|
4853
5136
|
return { command: 'help', topic: 'playbooks' };
|
|
@@ -7212,6 +7495,13 @@ async function runGitSetup(command, io, runtime) {
|
|
|
7212
7495
|
credentialHelper: helper,
|
|
7213
7496
|
});
|
|
7214
7497
|
}
|
|
7498
|
+
const memoryFetch = command.dryRun
|
|
7499
|
+
? { status: 'not_configured', ref: ARTIFACT_MEMORY_NOTES_REF, commitSha: null }
|
|
7500
|
+
: await fetchArtifactMemoryNotes({
|
|
7501
|
+
repoPath: root,
|
|
7502
|
+
remoteName: command.remoteName,
|
|
7503
|
+
runner: commandRunnerForMemory(runtime),
|
|
7504
|
+
});
|
|
7215
7505
|
const result = {
|
|
7216
7506
|
ok: true,
|
|
7217
7507
|
command: command.command,
|
|
@@ -7226,6 +7516,7 @@ async function runGitSetup(command, io, runtime) {
|
|
|
7226
7516
|
authSource: credentials.source,
|
|
7227
7517
|
profile: credentials.profileName,
|
|
7228
7518
|
credentialHelper: helper,
|
|
7519
|
+
memoryFetch,
|
|
7229
7520
|
commands: plannedCommands.map((parts) => parts.join(' ')),
|
|
7230
7521
|
next: `git push ${command.remoteName} ${git.defaultBranch}`,
|
|
7231
7522
|
};
|
|
@@ -7237,6 +7528,7 @@ async function runGitSetup(command, io, runtime) {
|
|
|
7237
7528
|
`remote: ${command.remoteName}`,
|
|
7238
7529
|
`remote_url: ${git.remoteUrl}`,
|
|
7239
7530
|
`default_branch: ${git.defaultBranch}`,
|
|
7531
|
+
`memory_fetch: ${memoryFetch.status}`,
|
|
7240
7532
|
`next: ${result.next}`,
|
|
7241
7533
|
].join('\n'));
|
|
7242
7534
|
}
|
|
@@ -7368,6 +7660,14 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
7368
7660
|
job: null,
|
|
7369
7661
|
result: null,
|
|
7370
7662
|
};
|
|
7663
|
+
let memoryFetch = {
|
|
7664
|
+
status: command.dryRun ? 'not_configured' : 'failed',
|
|
7665
|
+
ref: ARTIFACT_MEMORY_NOTES_REF,
|
|
7666
|
+
commitSha: null,
|
|
7667
|
+
...(command.dryRun ? {} : { error: 'not_attempted' }),
|
|
7668
|
+
};
|
|
7669
|
+
let memory = null;
|
|
7670
|
+
let memorySync = null;
|
|
7371
7671
|
let seedPaths = [];
|
|
7372
7672
|
let plannedFiles = await planContextFiles({
|
|
7373
7673
|
root,
|
|
@@ -7396,6 +7696,11 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
7396
7696
|
remoteUrl: git.remoteUrl,
|
|
7397
7697
|
credentialHelper: helper,
|
|
7398
7698
|
});
|
|
7699
|
+
memoryFetch = await fetchArtifactMemoryNotes({
|
|
7700
|
+
repoPath: root,
|
|
7701
|
+
remoteName: command.remoteName,
|
|
7702
|
+
runner: commandRunnerForMemory(runtime),
|
|
7703
|
+
});
|
|
7399
7704
|
sourceCheckout = await maybeCheckoutInitialArtifactSource({
|
|
7400
7705
|
runtime,
|
|
7401
7706
|
root,
|
|
@@ -7637,6 +7942,22 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
7637
7942
|
};
|
|
7638
7943
|
}
|
|
7639
7944
|
}
|
|
7945
|
+
memory = await appendAutomaticMemory({
|
|
7946
|
+
repoPath: root,
|
|
7947
|
+
artifactId: command.artifactId,
|
|
7948
|
+
kind: 'change',
|
|
7949
|
+
summary: 'Initialized local Tender app checkout',
|
|
7950
|
+
body: `Source checkout: ${sourceCheckout.skippedReason ?? (sourceCheckout.checkedOut ? 'checked_out' : 'not_checked_out')}\nSource export: ${sourceExport.skippedReason ?? sourceExport.source ?? 'none'}\nPreview: ${preview.skippedReason ?? (preview.attempted ? 'requested' : 'not_requested')}`,
|
|
7951
|
+
tags: ['init'],
|
|
7952
|
+
status: 'resolved',
|
|
7953
|
+
related: {
|
|
7954
|
+
files: seedPaths,
|
|
7955
|
+
commands: [command.command],
|
|
7956
|
+
...(preview.job?.jobId ? { releaseId: preview.job.jobId } : {}),
|
|
7957
|
+
},
|
|
7958
|
+
runtime,
|
|
7959
|
+
});
|
|
7960
|
+
memorySync = await flushAutomaticMemory({ repoPath: root, remoteName: command.remoteName, runtime });
|
|
7640
7961
|
}
|
|
7641
7962
|
const result = {
|
|
7642
7963
|
ok: true,
|
|
@@ -7657,6 +7978,9 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
7657
7978
|
sourceExport,
|
|
7658
7979
|
seedCommit,
|
|
7659
7980
|
preview,
|
|
7981
|
+
memoryFetch,
|
|
7982
|
+
memory,
|
|
7983
|
+
memorySync,
|
|
7660
7984
|
summary: {
|
|
7661
7985
|
create: plannedFiles.filter((file) => file.action === 'create').length,
|
|
7662
7986
|
overwrite: plannedFiles.filter((file) => file.action === 'overwrite').length,
|
|
@@ -7687,6 +8011,8 @@ async function runArtifactsInit(command, io, runtime) {
|
|
|
7687
8011
|
`dir: ${root}`,
|
|
7688
8012
|
`remote: ${command.remoteName}`,
|
|
7689
8013
|
preview.attempted && preview.job?.jobId ? `preview_job: ${preview.job.jobId}` : null,
|
|
8014
|
+
memorySync ? `memory_sync: ${memorySync.status}` : null,
|
|
8015
|
+
memorySync?.status === 'failed' ? `next: tender memory flush --dir ${command.dir}` : null,
|
|
7690
8016
|
`next: ${result.next}`,
|
|
7691
8017
|
]
|
|
7692
8018
|
.filter((line) => Boolean(line))
|
|
@@ -7729,6 +8055,26 @@ async function postLifecycle(input) {
|
|
|
7729
8055
|
io: input.io,
|
|
7730
8056
|
json: input.command.json,
|
|
7731
8057
|
});
|
|
8058
|
+
await appendAutomaticMemory({
|
|
8059
|
+
repoPath: process.cwd(),
|
|
8060
|
+
artifactId: input.command.artifactId,
|
|
8061
|
+
kind: input.operation === 'publish' ? 'change' : 'validation',
|
|
8062
|
+
summary: `${input.command.command} streamed`,
|
|
8063
|
+
body: `Operation: ${input.operation}\nStatus: streamed`,
|
|
8064
|
+
tags: [input.operation, 'stream'],
|
|
8065
|
+
status: 'resolved',
|
|
8066
|
+
related: {
|
|
8067
|
+
commands: [input.command.command],
|
|
8068
|
+
...('commitSha' in input.command && input.command.commitSha ? { releaseId: input.command.commitSha } : {}),
|
|
8069
|
+
},
|
|
8070
|
+
runtime: input.runtime,
|
|
8071
|
+
});
|
|
8072
|
+
if (input.operation === 'publish') {
|
|
8073
|
+
const memorySync = await flushAutomaticMemory({ repoPath: process.cwd(), remoteName: 'tender', runtime: input.runtime });
|
|
8074
|
+
if (memorySync.status === 'failed') {
|
|
8075
|
+
input.io.stderr(`Memory notes sync ${memorySync.status}. Next: tender memory flush --dir .`);
|
|
8076
|
+
}
|
|
8077
|
+
}
|
|
7732
8078
|
return 0;
|
|
7733
8079
|
}
|
|
7734
8080
|
const payload = await parseJsonResponse(response);
|
|
@@ -7747,8 +8093,36 @@ async function postLifecycle(input) {
|
|
|
7747
8093
|
job: payload.job ?? null,
|
|
7748
8094
|
result: payload.result ?? null,
|
|
7749
8095
|
};
|
|
8096
|
+
const repoPath = process.cwd();
|
|
8097
|
+
const changedFiles = await readLocalChangedFiles({ runtime: input.runtime, repoPath });
|
|
8098
|
+
const releaseGitRef = input.operation === 'publish' ? readReleaseGitRefFromResult(result.result) : null;
|
|
8099
|
+
const memory = await appendAutomaticMemory({
|
|
8100
|
+
repoPath,
|
|
8101
|
+
artifactId: input.command.artifactId,
|
|
8102
|
+
kind: input.operation === 'validate' ? 'validation' : input.operation === 'publish' ? 'change' : 'validation',
|
|
8103
|
+
summary: `${input.command.command} completed`,
|
|
8104
|
+
body: `Operation: ${result.operation}\nStatus: completed`,
|
|
8105
|
+
tags: [input.operation],
|
|
8106
|
+
status: 'resolved',
|
|
8107
|
+
related: {
|
|
8108
|
+
files: changedFiles,
|
|
8109
|
+
commands: [input.command.command],
|
|
8110
|
+
...(input.operation === 'preview' ? { previewUrl: readPreviewUrlFromJob(result.job) ?? undefined } : {}),
|
|
8111
|
+
...(input.operation === 'publish' ? { publishedUrl: readObjectString(readObject(result.result), 'publishedUrl') ?? undefined } : {}),
|
|
8112
|
+
...(releaseGitRef?.tagName ? { releaseId: releaseGitRef.tagName } : {}),
|
|
8113
|
+
},
|
|
8114
|
+
runtime: input.runtime,
|
|
8115
|
+
});
|
|
8116
|
+
const memorySync = input.operation === 'publish'
|
|
8117
|
+
? await flushAutomaticMemory({ repoPath, remoteName: 'tender', runtime: input.runtime })
|
|
8118
|
+
: null;
|
|
8119
|
+
const resultWithMemory = {
|
|
8120
|
+
...result,
|
|
8121
|
+
memory,
|
|
8122
|
+
...(memorySync ? { memorySync } : {}),
|
|
8123
|
+
};
|
|
7750
8124
|
if (input.command.json) {
|
|
7751
|
-
input.io.stdout(JSON.stringify(
|
|
8125
|
+
input.io.stdout(JSON.stringify(resultWithMemory, null, 2));
|
|
7752
8126
|
}
|
|
7753
8127
|
else {
|
|
7754
8128
|
input.io.stdout([
|
|
@@ -7756,6 +8130,8 @@ async function postLifecycle(input) {
|
|
|
7756
8130
|
`operation: ${result.operation}`,
|
|
7757
8131
|
'status: completed',
|
|
7758
8132
|
...(input.operation === 'publish' ? formatPublishResultLines(result.result) : []),
|
|
8133
|
+
memorySync ? `memory_sync: ${memorySync.status}` : null,
|
|
8134
|
+
memorySync?.status === 'failed' ? `next: tender memory flush --dir .` : null,
|
|
7759
8135
|
].join('\n'));
|
|
7760
8136
|
}
|
|
7761
8137
|
return 0;
|
|
@@ -8337,6 +8713,283 @@ async function runGitRemote(command, io, runtime) {
|
|
|
8337
8713
|
}
|
|
8338
8714
|
return 0;
|
|
8339
8715
|
}
|
|
8716
|
+
function commandRunnerForMemory(runtime) {
|
|
8717
|
+
return runtime.commandRunner ?? defaultCommandRunner;
|
|
8718
|
+
}
|
|
8719
|
+
function memoryRecoveryForArtifact(artifactId, dir) {
|
|
8720
|
+
return artifactId ? `tender app git setup ${artifactId} --dir ${dir}` : 'tender app git setup <app-id> --dir .';
|
|
8721
|
+
}
|
|
8722
|
+
function formatMemoryAppendResult(result) {
|
|
8723
|
+
return {
|
|
8724
|
+
entry: result.entry,
|
|
8725
|
+
warnings: result.warnings,
|
|
8726
|
+
noteBytes: result.noteBytes,
|
|
8727
|
+
ref: ARTIFACT_MEMORY_NOTES_REF,
|
|
8728
|
+
};
|
|
8729
|
+
}
|
|
8730
|
+
async function appendCliMemoryEntry(input) {
|
|
8731
|
+
const repoPath = path.resolve(input.command.dir);
|
|
8732
|
+
try {
|
|
8733
|
+
return await appendArtifactMemoryNote({
|
|
8734
|
+
repoPath,
|
|
8735
|
+
artifactId: input.command.artifactId,
|
|
8736
|
+
kind: input.kind,
|
|
8737
|
+
summary: input.summary,
|
|
8738
|
+
body: input.body,
|
|
8739
|
+
tags: input.tags,
|
|
8740
|
+
status: input.status,
|
|
8741
|
+
severity: input.severity,
|
|
8742
|
+
source: {
|
|
8743
|
+
actor: 'agent',
|
|
8744
|
+
command: input.command.command,
|
|
8745
|
+
},
|
|
8746
|
+
runner: commandRunnerForMemory(input.runtime),
|
|
8747
|
+
now: input.runtime.now,
|
|
8748
|
+
});
|
|
8749
|
+
}
|
|
8750
|
+
catch (error) {
|
|
8751
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
8752
|
+
if (message.includes('\nNext:')) {
|
|
8753
|
+
throw error;
|
|
8754
|
+
}
|
|
8755
|
+
throw new Error(`${message}\nNext: ${memoryRecoveryForArtifact(input.command.artifactId, input.command.dir)}`);
|
|
8756
|
+
}
|
|
8757
|
+
}
|
|
8758
|
+
async function runMemoryStart(command, io, runtime) {
|
|
8759
|
+
const appended = await appendCliMemoryEntry({
|
|
8760
|
+
command,
|
|
8761
|
+
kind: 'intent',
|
|
8762
|
+
summary: command.summary,
|
|
8763
|
+
body: command.body,
|
|
8764
|
+
tags: ['session'],
|
|
8765
|
+
runtime,
|
|
8766
|
+
});
|
|
8767
|
+
const result = {
|
|
8768
|
+
ok: true,
|
|
8769
|
+
command: command.command,
|
|
8770
|
+
...formatMemoryAppendResult(appended),
|
|
8771
|
+
};
|
|
8772
|
+
if (command.json) {
|
|
8773
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
8774
|
+
}
|
|
8775
|
+
else {
|
|
8776
|
+
io.stdout([`memory: ${appended.entry.kind}`, `summary: ${appended.entry.summary}`, `session: ${appended.entry.sessionId}`].join('\n'));
|
|
8777
|
+
}
|
|
8778
|
+
return 0;
|
|
8779
|
+
}
|
|
8780
|
+
async function runMemoryAdd(command, io, runtime) {
|
|
8781
|
+
const appended = await appendCliMemoryEntry({
|
|
8782
|
+
command,
|
|
8783
|
+
kind: command.kind,
|
|
8784
|
+
summary: command.summary,
|
|
8785
|
+
body: command.body,
|
|
8786
|
+
tags: command.tags,
|
|
8787
|
+
runtime,
|
|
8788
|
+
});
|
|
8789
|
+
const result = {
|
|
8790
|
+
ok: true,
|
|
8791
|
+
command: command.command,
|
|
8792
|
+
...formatMemoryAppendResult(appended),
|
|
8793
|
+
};
|
|
8794
|
+
if (command.json) {
|
|
8795
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
8796
|
+
}
|
|
8797
|
+
else {
|
|
8798
|
+
io.stdout([`memory: ${appended.entry.kind}`, `summary: ${appended.entry.summary}`].join('\n'));
|
|
8799
|
+
}
|
|
8800
|
+
return 0;
|
|
8801
|
+
}
|
|
8802
|
+
async function inferHandoffFromRecentEntries(input) {
|
|
8803
|
+
const entries = await readArtifactMemoryEntries({
|
|
8804
|
+
repoPath: input.repoPath,
|
|
8805
|
+
commitSha: input.commitSha,
|
|
8806
|
+
runner: commandRunnerForMemory(input.runtime),
|
|
8807
|
+
}).catch(() => []);
|
|
8808
|
+
const relevant = [...entries].reverse().find((entry) => entry.kind === 'failure' || entry.kind === 'open_question');
|
|
8809
|
+
return relevant?.summary ?? null;
|
|
8810
|
+
}
|
|
8811
|
+
async function runMemoryHandoff(command, io, runtime) {
|
|
8812
|
+
const repoPath = path.resolve(command.dir);
|
|
8813
|
+
const head = await readArtifactMemoryHead({
|
|
8814
|
+
repoPath,
|
|
8815
|
+
runner: commandRunnerForMemory(runtime),
|
|
8816
|
+
}).catch(() => null);
|
|
8817
|
+
const inferredReason = command.reason ?? (await inferHandoffFromRecentEntries({ repoPath, runtime, commitSha: head }));
|
|
8818
|
+
if (!inferredReason && !command.summary) {
|
|
8819
|
+
throw new TenderCliUsageError('memory handoff needs --reason or a recent failure/open_question entry to infer from.\nNext: tender memory handoff --reason "Need Tender support because ..." --summary "Current blocker and what was tried"');
|
|
8820
|
+
}
|
|
8821
|
+
const summary = command.summary ?? inferredReason ?? 'Tender support handoff';
|
|
8822
|
+
const body = [
|
|
8823
|
+
inferredReason ? `Reason: ${inferredReason}` : null,
|
|
8824
|
+
command.body,
|
|
8825
|
+
'Include goal, current failure, commands tried, changed files, suspected causes, and what not to retry.',
|
|
8826
|
+
]
|
|
8827
|
+
.filter((line) => Boolean(line && line.trim()))
|
|
8828
|
+
.join('\n');
|
|
8829
|
+
const appended = await appendCliMemoryEntry({
|
|
8830
|
+
command,
|
|
8831
|
+
kind: 'support_handoff',
|
|
8832
|
+
summary,
|
|
8833
|
+
body,
|
|
8834
|
+
tags: ['support', 'handoff'],
|
|
8835
|
+
status: 'open',
|
|
8836
|
+
severity: 'warning',
|
|
8837
|
+
runtime,
|
|
8838
|
+
});
|
|
8839
|
+
const result = {
|
|
8840
|
+
ok: true,
|
|
8841
|
+
command: command.command,
|
|
8842
|
+
reason: inferredReason,
|
|
8843
|
+
...formatMemoryAppendResult(appended),
|
|
8844
|
+
};
|
|
8845
|
+
if (command.json) {
|
|
8846
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
8847
|
+
}
|
|
8848
|
+
else {
|
|
8849
|
+
io.stdout([`memory: support_handoff`, `summary: ${appended.entry.summary}`, `next: tender memory flush --dir ${command.dir}`].join('\n'));
|
|
8850
|
+
}
|
|
8851
|
+
return 0;
|
|
8852
|
+
}
|
|
8853
|
+
async function runMemoryShow(command, io, runtime) {
|
|
8854
|
+
const repoPath = path.resolve(command.dir);
|
|
8855
|
+
const entries = await readArtifactMemoryEntries({
|
|
8856
|
+
repoPath,
|
|
8857
|
+
commitSha: command.commitSha,
|
|
8858
|
+
runner: commandRunnerForMemory(runtime),
|
|
8859
|
+
});
|
|
8860
|
+
const result = {
|
|
8861
|
+
ok: true,
|
|
8862
|
+
command: command.command,
|
|
8863
|
+
ref: ARTIFACT_MEMORY_NOTES_REF,
|
|
8864
|
+
commitSha: command.commitSha ?? (await readArtifactMemoryHead({ repoPath, runner: commandRunnerForMemory(runtime) })),
|
|
8865
|
+
entries,
|
|
8866
|
+
};
|
|
8867
|
+
if (command.json) {
|
|
8868
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
8869
|
+
}
|
|
8870
|
+
else {
|
|
8871
|
+
io.stdout(entries.length > 0 ? entries.map(formatArtifactMemoryEntry).join('\n') : 'No artifact memory entries found for this commit.');
|
|
8872
|
+
}
|
|
8873
|
+
return 0;
|
|
8874
|
+
}
|
|
8875
|
+
async function runMemoryBrief(command, io, runtime) {
|
|
8876
|
+
const repoPath = path.resolve(command.dir);
|
|
8877
|
+
const entries = await readArtifactMemoryEntries({
|
|
8878
|
+
repoPath,
|
|
8879
|
+
runner: commandRunnerForMemory(runtime),
|
|
8880
|
+
});
|
|
8881
|
+
const latestSummary = [...entries].reverse().find((entry) => entry.kind === 'summary') ?? null;
|
|
8882
|
+
const unresolved = entries
|
|
8883
|
+
.filter((entry) => entry.status === 'open' || entry.kind === 'failure' || entry.kind === 'open_question' || entry.kind === 'support_handoff')
|
|
8884
|
+
.slice(-command.limit);
|
|
8885
|
+
const briefEntries = latestSummary ? [latestSummary, ...unresolved.filter((entry) => entry.entryId !== latestSummary.entryId)] : unresolved;
|
|
8886
|
+
const result = {
|
|
8887
|
+
ok: true,
|
|
8888
|
+
command: command.command,
|
|
8889
|
+
ref: ARTIFACT_MEMORY_NOTES_REF,
|
|
8890
|
+
entries: briefEntries,
|
|
8891
|
+
};
|
|
8892
|
+
if (command.json) {
|
|
8893
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
8894
|
+
}
|
|
8895
|
+
else {
|
|
8896
|
+
io.stdout(briefEntries.length > 0 ? briefEntries.map(formatArtifactMemoryEntry).join('\n') : 'No artifact memory brief is available yet.');
|
|
8897
|
+
}
|
|
8898
|
+
return 0;
|
|
8899
|
+
}
|
|
8900
|
+
async function runMemoryFlush(command, io, runtime) {
|
|
8901
|
+
const sync = await pushArtifactMemoryNotes({
|
|
8902
|
+
repoPath: path.resolve(command.dir),
|
|
8903
|
+
remoteName: command.remoteName,
|
|
8904
|
+
runner: commandRunnerForMemory(runtime),
|
|
8905
|
+
});
|
|
8906
|
+
const next = sync.status === 'failed'
|
|
8907
|
+
? `tender memory flush --dir ${command.dir} --remote ${command.remoteName}`
|
|
8908
|
+
: sync.status === 'not_configured'
|
|
8909
|
+
? memoryRecoveryForArtifact(command.artifactId, command.dir)
|
|
8910
|
+
: null;
|
|
8911
|
+
const result = {
|
|
8912
|
+
ok: sync.status === 'pushed',
|
|
8913
|
+
command: command.command,
|
|
8914
|
+
memorySync: sync,
|
|
8915
|
+
next,
|
|
8916
|
+
};
|
|
8917
|
+
if (command.json) {
|
|
8918
|
+
io.stdout(JSON.stringify(result, null, 2));
|
|
8919
|
+
}
|
|
8920
|
+
else {
|
|
8921
|
+
io.stdout([
|
|
8922
|
+
`memory_sync: ${sync.status}`,
|
|
8923
|
+
sync.error ? `error: ${sync.error}` : null,
|
|
8924
|
+
result.next ? `next: ${result.next}` : null,
|
|
8925
|
+
]
|
|
8926
|
+
.filter((line) => Boolean(line))
|
|
8927
|
+
.join('\n'));
|
|
8928
|
+
}
|
|
8929
|
+
return sync.status === 'pushed' ? 0 : 1;
|
|
8930
|
+
}
|
|
8931
|
+
async function runMemoryExport(command, io, runtime) {
|
|
8932
|
+
const entries = await readArtifactMemoryEntries({
|
|
8933
|
+
repoPath: path.resolve(command.dir),
|
|
8934
|
+
commitSha: command.commitSha,
|
|
8935
|
+
runner: commandRunnerForMemory(runtime),
|
|
8936
|
+
});
|
|
8937
|
+
if (command.format === 'jsonl') {
|
|
8938
|
+
io.stdout(entries.map((entry) => JSON.stringify(entry)).join('\n'));
|
|
8939
|
+
return 0;
|
|
8940
|
+
}
|
|
8941
|
+
throw new TenderCliUsageError(`Unsupported memory export format: ${command.format}.\nNext: tender memory export --format jsonl --dir ${command.dir}`);
|
|
8942
|
+
}
|
|
8943
|
+
async function readLocalChangedFiles(input) {
|
|
8944
|
+
const result = await runGitCommand({
|
|
8945
|
+
runtime: input.runtime,
|
|
8946
|
+
cwd: input.repoPath,
|
|
8947
|
+
args: ['status', '--porcelain'],
|
|
8948
|
+
allowFailure: true,
|
|
8949
|
+
});
|
|
8950
|
+
if (result.exitCode !== 0) {
|
|
8951
|
+
return [];
|
|
8952
|
+
}
|
|
8953
|
+
return result.stdout
|
|
8954
|
+
.split(/\r?\n/)
|
|
8955
|
+
.map((line) => line.slice(3).trim())
|
|
8956
|
+
.filter(Boolean)
|
|
8957
|
+
.slice(0, 50);
|
|
8958
|
+
}
|
|
8959
|
+
async function appendAutomaticMemory(input) {
|
|
8960
|
+
try {
|
|
8961
|
+
return await appendArtifactMemoryNote({
|
|
8962
|
+
repoPath: input.repoPath,
|
|
8963
|
+
artifactId: input.artifactId,
|
|
8964
|
+
kind: input.kind,
|
|
8965
|
+
summary: input.summary,
|
|
8966
|
+
body: input.body,
|
|
8967
|
+
tags: input.tags,
|
|
8968
|
+
severity: input.severity,
|
|
8969
|
+
status: input.status,
|
|
8970
|
+
related: input.related,
|
|
8971
|
+
source: {
|
|
8972
|
+
actor: 'cli',
|
|
8973
|
+
command: input.summary,
|
|
8974
|
+
},
|
|
8975
|
+
runner: commandRunnerForMemory(input.runtime),
|
|
8976
|
+
now: input.runtime.now,
|
|
8977
|
+
});
|
|
8978
|
+
}
|
|
8979
|
+
catch (error) {
|
|
8980
|
+
return {
|
|
8981
|
+
error: error instanceof Error ? error.message : String(error),
|
|
8982
|
+
next: `tender memory add --dir ${input.repoPath} --artifact ${input.artifactId} --kind ${input.kind} --summary ${JSON.stringify(input.summary)}`,
|
|
8983
|
+
};
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8986
|
+
async function flushAutomaticMemory(input) {
|
|
8987
|
+
return pushArtifactMemoryNotes({
|
|
8988
|
+
repoPath: input.repoPath,
|
|
8989
|
+
remoteName: input.remoteName,
|
|
8990
|
+
runner: commandRunnerForMemory(input.runtime),
|
|
8991
|
+
});
|
|
8992
|
+
}
|
|
8340
8993
|
async function resolveAnalyticsCredentials(command, runtime) {
|
|
8341
8994
|
return await resolveApiCredentials({
|
|
8342
8995
|
baseUrl: command.baseUrl,
|
|
@@ -9026,6 +9679,30 @@ export async function runTenderCli(args, io = {
|
|
|
9026
9679
|
else if (command.topic === 'auth status') {
|
|
9027
9680
|
io.stdout(authStatusHelp());
|
|
9028
9681
|
}
|
|
9682
|
+
else if (command.topic === 'memory') {
|
|
9683
|
+
io.stdout(memoryHelp());
|
|
9684
|
+
}
|
|
9685
|
+
else if (command.topic === 'memory start') {
|
|
9686
|
+
io.stdout(memoryHelp());
|
|
9687
|
+
}
|
|
9688
|
+
else if (command.topic === 'memory add') {
|
|
9689
|
+
io.stdout(memoryAddHelp());
|
|
9690
|
+
}
|
|
9691
|
+
else if (command.topic === 'memory handoff') {
|
|
9692
|
+
io.stdout(memoryHandoffHelp());
|
|
9693
|
+
}
|
|
9694
|
+
else if (command.topic === 'memory show') {
|
|
9695
|
+
io.stdout(memoryHelp());
|
|
9696
|
+
}
|
|
9697
|
+
else if (command.topic === 'memory brief') {
|
|
9698
|
+
io.stdout(memoryHelp());
|
|
9699
|
+
}
|
|
9700
|
+
else if (command.topic === 'memory flush') {
|
|
9701
|
+
io.stdout(memoryHelp());
|
|
9702
|
+
}
|
|
9703
|
+
else if (command.topic === 'memory export') {
|
|
9704
|
+
io.stdout(memoryHelp());
|
|
9705
|
+
}
|
|
9029
9706
|
else if (command.topic === 'playbooks') {
|
|
9030
9707
|
io.stdout(playbooksHelp());
|
|
9031
9708
|
}
|
|
@@ -9208,6 +9885,27 @@ export async function runTenderCli(args, io = {
|
|
|
9208
9885
|
if (command.command === 'auth status') {
|
|
9209
9886
|
return await runAuthStatus(command, io, runtime);
|
|
9210
9887
|
}
|
|
9888
|
+
if (command.command === 'memory start') {
|
|
9889
|
+
return await runMemoryStart(command, io, runtime);
|
|
9890
|
+
}
|
|
9891
|
+
if (command.command === 'memory add') {
|
|
9892
|
+
return await runMemoryAdd(command, io, runtime);
|
|
9893
|
+
}
|
|
9894
|
+
if (command.command === 'memory handoff') {
|
|
9895
|
+
return await runMemoryHandoff(command, io, runtime);
|
|
9896
|
+
}
|
|
9897
|
+
if (command.command === 'memory show') {
|
|
9898
|
+
return await runMemoryShow(command, io, runtime);
|
|
9899
|
+
}
|
|
9900
|
+
if (command.command === 'memory brief') {
|
|
9901
|
+
return await runMemoryBrief(command, io, runtime);
|
|
9902
|
+
}
|
|
9903
|
+
if (command.command === 'memory flush') {
|
|
9904
|
+
return await runMemoryFlush(command, io, runtime);
|
|
9905
|
+
}
|
|
9906
|
+
if (command.command === 'memory export') {
|
|
9907
|
+
return await runMemoryExport(command, io, runtime);
|
|
9908
|
+
}
|
|
9211
9909
|
if (command.command === 'playbooks list') {
|
|
9212
9910
|
return await runPlaybooksList(command, io, runtime);
|
|
9213
9911
|
}
|