gsdd-cli 0.21.0 → 0.23.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 +3 -1
- package/agents/executor.md +12 -1
- package/agents/planner.md +2 -0
- package/agents/verifier.md +1 -1
- package/bin/gsdd.mjs +3 -2
- package/bin/lib/control-map.mjs +629 -0
- package/bin/lib/health.mjs +2 -2
- package/bin/lib/init-flow.mjs +7 -7
- package/bin/lib/init-runtime.mjs +3 -0
- package/bin/lib/rendering.mjs +5 -0
- package/bin/lib/ui-proof.mjs +136 -13
- package/distilled/DESIGN.md +63 -7
- package/distilled/EVIDENCE-INDEX.md +23 -0
- package/distilled/README.md +9 -0
- package/distilled/templates/delegates/plan-checker.md +1 -0
- package/distilled/templates/ui-proof.md +33 -7
- package/distilled/workflows/execute.md +5 -4
- package/distilled/workflows/pause.md +2 -0
- package/distilled/workflows/plan.md +7 -6
- package/distilled/workflows/progress.md +4 -0
- package/distilled/workflows/quick.md +6 -3
- package/distilled/workflows/resume.md +4 -0
- package/distilled/workflows/verify.md +1 -0
- package/docs/USER-GUIDE.md +2 -0
- package/package.json +2 -2
package/bin/lib/init-flow.mjs
CHANGED
|
@@ -100,6 +100,7 @@ export function createCmdInit(ctx) {
|
|
|
100
100
|
promptApi,
|
|
101
101
|
preselectedConfig: interactiveSession.config,
|
|
102
102
|
});
|
|
103
|
+
ensureGitignoreEntry(ctx.cwd, '.planning/.local/', ' - ensured .planning/.local/ is gitignored');
|
|
103
104
|
|
|
104
105
|
if (briefSource) {
|
|
105
106
|
cpSync(briefSource, join(ctx.planningDir, 'PROJECT_BRIEF.md'));
|
|
@@ -273,7 +274,7 @@ async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedCo
|
|
|
273
274
|
if (preselectedConfig) {
|
|
274
275
|
writeFileSync(configFile, JSON.stringify(preselectedConfig, null, 2));
|
|
275
276
|
console.log(' - saved .planning/config.json (guided wizard)\n');
|
|
276
|
-
if (!preselectedConfig.commitDocs) ensureGitignoreEntry(cwd);
|
|
277
|
+
if (!preselectedConfig.commitDocs) ensureGitignoreEntry(cwd, '.planning/', ' - ensured .planning/ is gitignored');
|
|
277
278
|
return;
|
|
278
279
|
}
|
|
279
280
|
|
|
@@ -281,7 +282,7 @@ async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedCo
|
|
|
281
282
|
const config = buildDefaultConfig({ autoAdvance: true });
|
|
282
283
|
writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
283
284
|
console.log(' - wrote .planning/config.json (auto defaults)\n');
|
|
284
|
-
if (!config.commitDocs) ensureGitignoreEntry(cwd);
|
|
285
|
+
if (!config.commitDocs) ensureGitignoreEntry(cwd, '.planning/', ' - ensured .planning/ is gitignored');
|
|
285
286
|
return;
|
|
286
287
|
}
|
|
287
288
|
|
|
@@ -289,7 +290,7 @@ async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedCo
|
|
|
289
290
|
const config = buildDefaultConfig({ autoAdvance: false });
|
|
290
291
|
writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
291
292
|
console.log(' - wrote .planning/config.json (non-interactive defaults)\n');
|
|
292
|
-
if (!config.commitDocs) ensureGitignoreEntry(cwd);
|
|
293
|
+
if (!config.commitDocs) ensureGitignoreEntry(cwd, '.planning/', ' - ensured .planning/ is gitignored');
|
|
293
294
|
return;
|
|
294
295
|
}
|
|
295
296
|
|
|
@@ -303,18 +304,17 @@ async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedCo
|
|
|
303
304
|
|
|
304
305
|
writeFileSync(configFile, JSON.stringify(selected, null, 2));
|
|
305
306
|
console.log(' - saved .planning/config.json (guided wizard)\n');
|
|
306
|
-
if (!selected.commitDocs) ensureGitignoreEntry(cwd);
|
|
307
|
+
if (!selected.commitDocs) ensureGitignoreEntry(cwd, '.planning/', ' - ensured .planning/ is gitignored');
|
|
307
308
|
}
|
|
308
309
|
|
|
309
|
-
function ensureGitignoreEntry(cwd) {
|
|
310
|
+
function ensureGitignoreEntry(cwd, entry, message) {
|
|
310
311
|
const gitignorePath = join(cwd, '.gitignore');
|
|
311
|
-
const entry = '.planning/';
|
|
312
312
|
const hasGitignore = existsSync(gitignorePath);
|
|
313
313
|
const current = hasGitignore ? readFileSync(gitignorePath, 'utf-8') : '';
|
|
314
314
|
if (!current.split(/\r?\n/).includes(entry)) {
|
|
315
315
|
const next = current.trimEnd() ? `${current.trimEnd()}\n${entry}\n` : `${entry}\n`;
|
|
316
316
|
writeFileSync(gitignorePath, next);
|
|
317
|
-
console.log(
|
|
317
|
+
console.log(message);
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
320
|
|
package/bin/lib/init-runtime.mjs
CHANGED
|
@@ -192,6 +192,8 @@ Commands:
|
|
|
192
192
|
Validate UI proof metadata; use --claim for stronger proof uses
|
|
193
193
|
ui-proof compare <planned-slots-json> [observed-bundle-json ...]
|
|
194
194
|
Compare planned UI proof slots against observed bundles
|
|
195
|
+
control-map [--json] [--with-ignored] [--annotations <path>]
|
|
196
|
+
Report computed repo/worktree/planning state and local annotations
|
|
195
197
|
help Show this summary
|
|
196
198
|
|
|
197
199
|
Platforms (for --tools):
|
|
@@ -263,6 +265,7 @@ Advanced/internal helpers (kept available, but not the primary first-run user st
|
|
|
263
265
|
session-fingerprint Rebaseline the local planning-state fingerprint after review
|
|
264
266
|
phase-status Update ROADMAP.md phase status through the local helper surface
|
|
265
267
|
ui-proof Validate UI proof metadata and compare planned slots to observed bundles
|
|
268
|
+
control-map Report computed repo/worktree/planning state and local annotations
|
|
266
269
|
file-op Deterministic workspace-confined file copy/delete/text mutation
|
|
267
270
|
`;
|
|
268
271
|
}
|
package/bin/lib/rendering.mjs
CHANGED
|
@@ -7,6 +7,7 @@ const __dirname = dirname(__filename);
|
|
|
7
7
|
const DISTILLED_DIR = join(__dirname, '..', '..', 'distilled');
|
|
8
8
|
const HELPER_LIB_FILES = Object.freeze([
|
|
9
9
|
'cli-utils.mjs',
|
|
10
|
+
'control-map.mjs',
|
|
10
11
|
'evidence-contract.mjs',
|
|
11
12
|
'file-ops.mjs',
|
|
12
13
|
'lifecycle-preflight.mjs',
|
|
@@ -49,6 +50,7 @@ import { cmdLifecyclePreflight } from './lib/lifecycle-preflight.mjs';
|
|
|
49
50
|
import { cmdPhaseStatus } from './lib/phase.mjs';
|
|
50
51
|
import { cmdSessionFingerprint } from './lib/session-fingerprint.mjs';
|
|
51
52
|
import { cmdUiProof } from './lib/ui-proof.mjs';
|
|
53
|
+
import { cmdControlMap } from './lib/control-map.mjs';
|
|
52
54
|
import { bootstrapHelperWorkspace, consumeWorkspaceRootArg, resolveWorkspaceContext } from './lib/workspace-root.mjs';
|
|
53
55
|
|
|
54
56
|
const COMMANDS = {
|
|
@@ -57,6 +59,7 @@ const COMMANDS = {
|
|
|
57
59
|
'phase-status': cmdPhaseStatus,
|
|
58
60
|
'session-fingerprint': cmdSessionFingerprint,
|
|
59
61
|
'ui-proof': cmdUiProof,
|
|
62
|
+
'control-map': cmdControlMap,
|
|
60
63
|
};
|
|
61
64
|
|
|
62
65
|
function printHelp() {
|
|
@@ -78,6 +81,8 @@ function printHelp() {
|
|
|
78
81
|
' Validate UI proof metadata; use --claim for stronger proof uses',
|
|
79
82
|
' ui-proof compare <planned-slots-json> [observed-bundle-json ...]',
|
|
80
83
|
' Compare planned UI proof slots against observed bundles',
|
|
84
|
+
' control-map [--json] [--with-ignored] [--annotations <path>]',
|
|
85
|
+
' Report computed repo/worktree/planning state and local annotations',
|
|
81
86
|
'',
|
|
82
87
|
'Advanced option:',
|
|
83
88
|
' --workspace-root <path> Override workspace root discovery before or after the subcommand',
|
package/bin/lib/ui-proof.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
-
import { isAbsolute, join, relative, resolve } from 'path';
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'path';
|
|
3
3
|
import { output } from './cli-utils.mjs';
|
|
4
4
|
import { resolveWorkspaceContext } from './workspace-root.mjs';
|
|
5
5
|
|
|
@@ -10,6 +10,8 @@ const ARTIFACT_VISIBILITIES = Object.freeze(['local_only', 'repo_tracked', 'publ
|
|
|
10
10
|
const RAW_ARTIFACT_TYPES = Object.freeze(['screenshot', 'trace', 'video', 'dom_snapshot', 'dom-snapshot', 'dom', 'report']);
|
|
11
11
|
const PUBLIC_CLAIM_USES = Object.freeze(['public', 'publication', 'tracked', 'delivery', 'release']);
|
|
12
12
|
const CLAIM_USES = Object.freeze([...PUBLIC_CLAIM_USES, 'local', 'local_only']);
|
|
13
|
+
const FAILURE_CLASSIFICATIONS = Object.freeze(['product_bug', 'missing_infra', 'flaky_harness', 'ambiguous_spec']);
|
|
14
|
+
const TOOL_ID_PATTERN = /^[a-z0-9][a-z0-9_.:-]*$/;
|
|
13
15
|
const REQUIRED_BUNDLE_FIELDS = Object.freeze([
|
|
14
16
|
'proof_bundle_version',
|
|
15
17
|
'scope',
|
|
@@ -25,6 +27,19 @@ const REQUIRED_BUNDLE_FIELDS = Object.freeze([
|
|
|
25
27
|
'claim_limits',
|
|
26
28
|
]);
|
|
27
29
|
const REQUIRED_SCOPE_FIELDS = Object.freeze(['work_item', 'claim', 'requirement_ids', 'slot_ids']);
|
|
30
|
+
const REQUIRED_SLOT_FIELDS = Object.freeze([
|
|
31
|
+
'slot_id',
|
|
32
|
+
'claim',
|
|
33
|
+
'route_state',
|
|
34
|
+
'required_evidence_kinds',
|
|
35
|
+
'minimum_observations',
|
|
36
|
+
'environment',
|
|
37
|
+
'viewport',
|
|
38
|
+
'expected_artifact_types',
|
|
39
|
+
'validation_command',
|
|
40
|
+
'manual_acceptance_required',
|
|
41
|
+
'claim_limit',
|
|
42
|
+
]);
|
|
28
43
|
const REQUIRED_ARTIFACT_FIELDS = Object.freeze(['visibility', 'retention', 'sensitivity', 'safe_to_publish']);
|
|
29
44
|
const REQUIRED_OBSERVATION_FIELDS = Object.freeze(['observation', 'claim', 'route_state', 'evidence_kind', 'artifact_refs', 'privacy', 'result', 'claim_limit']);
|
|
30
45
|
const REQUIRED_PRIVACY_FIELDS = Object.freeze(['data_classification', 'raw_artifacts_safe_to_publish', 'retention']);
|
|
@@ -72,12 +87,12 @@ function normalizeArray(value) {
|
|
|
72
87
|
|
|
73
88
|
function artifactType(artifact) {
|
|
74
89
|
const explicit = typeof artifact.type === 'string' ? artifact.type.toLowerCase() : '';
|
|
75
|
-
const
|
|
76
|
-
if (/screenshot|\.png$|\.jpe?g$|\.webp$/.test(
|
|
77
|
-
if (/trace|\.zip$/.test(
|
|
78
|
-
if (/video|\.mp4$|\.webm$|\.mov$/.test(
|
|
79
|
-
if (/dom|\.html?$/.test(
|
|
80
|
-
if (/report/.test(
|
|
90
|
+
const artifactRef = artifactReference(artifact)?.toLowerCase() || '';
|
|
91
|
+
if (/screenshot|\.png$|\.jpe?g$|\.webp$/.test(artifactRef)) return 'screenshot';
|
|
92
|
+
if (/trace|\.zip$/.test(artifactRef)) return 'trace';
|
|
93
|
+
if (/video|\.mp4$|\.webm$|\.mov$/.test(artifactRef)) return 'video';
|
|
94
|
+
if (/dom|\.html?$/.test(artifactRef)) return 'dom_snapshot';
|
|
95
|
+
if (/report/.test(artifactRef)) return 'report';
|
|
81
96
|
return explicit;
|
|
82
97
|
}
|
|
83
98
|
|
|
@@ -172,6 +187,19 @@ function validateEvidenceKinds(bundle, errors) {
|
|
|
172
187
|
}
|
|
173
188
|
}
|
|
174
189
|
|
|
190
|
+
function validateToolsUsed(bundle, errors) {
|
|
191
|
+
const tools = normalizeArray(bundle?.evidence_inputs?.tools_used);
|
|
192
|
+
if (tools.length === 0) {
|
|
193
|
+
addError(errors, 'missing_tools_used', 'evidence_inputs.tools_used', 'Missing UI proof tool provenance.', 'Record concise tool IDs such as browser, playwright, manual, or project-specific command IDs.');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
for (const [index, tool] of tools.entries()) {
|
|
197
|
+
if (!TOOL_ID_PATTERN.test(tool)) {
|
|
198
|
+
addError(errors, 'invalid_tool_id', `evidence_inputs.tools_used[${index}]`, `Invalid UI proof tool identifier: ${tool}`, 'Use a concise lowercase identifier without spaces, for example browser, playwright, manual, or gsdd-ui-proof-validate.');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
175
203
|
function validateResult(bundle, errors) {
|
|
176
204
|
if (!isPlainObject(bundle?.result)) return;
|
|
177
205
|
if (!hasValue(bundle.result.claim_status)) {
|
|
@@ -181,6 +209,28 @@ function validateResult(bundle, errors) {
|
|
|
181
209
|
}
|
|
182
210
|
}
|
|
183
211
|
|
|
212
|
+
function validateFailureClassification(bundle, errors) {
|
|
213
|
+
const statuses = [
|
|
214
|
+
bundle?.result?.claim_status,
|
|
215
|
+
...Object.values(isPlainObject(bundle?.result?.comparison_status_by_slot) ? bundle.result.comparison_status_by_slot : {}),
|
|
216
|
+
...normalizeArray(bundle?.observations).map((observation) => isPlainObject(observation) ? observation.result : null),
|
|
217
|
+
...normalizeArray(bundle?.commands_or_manual_steps).map((step) => isPlainObject(step) ? step.result : null),
|
|
218
|
+
].filter(Boolean);
|
|
219
|
+
const failedOrPartial = statuses.some((status) => status === 'failed' || status === 'partial');
|
|
220
|
+
const classifications = normalizeArray(bundle?.result?.failure_classification || bundle?.result?.failure_classifications);
|
|
221
|
+
|
|
222
|
+
if (failedOrPartial && classifications.length === 0) {
|
|
223
|
+
addError(errors, 'missing_failure_classification', 'result.failure_classification', 'Failed or partial UI proof must classify why it failed.', `Use one of: ${FAILURE_CLASSIFICATIONS.join(', ')}.`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (const [index, classification] of classifications.entries()) {
|
|
228
|
+
if (!FAILURE_CLASSIFICATIONS.includes(classification)) {
|
|
229
|
+
addError(errors, 'invalid_failure_classification', `result.failure_classification[${index}]`, `Invalid UI proof failure classification: ${classification}`, `Use only: ${FAILURE_CLASSIFICATIONS.join(', ')}.`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
184
234
|
function validateComparisonStatuses(bundle, errors) {
|
|
185
235
|
const statuses = bundle?.result?.comparison_status_by_slot;
|
|
186
236
|
if (!isPlainObject(statuses)) {
|
|
@@ -202,6 +252,10 @@ function validateComparisonStatuses(bundle, errors) {
|
|
|
202
252
|
addError(errors, 'invalid_comparison_status', `result.comparison_status_by_slot.${slot}`, `Invalid UI proof comparison status: ${status}`, `Use only: ${COMPARISON_STATUSES.join(', ')}.`);
|
|
203
253
|
}
|
|
204
254
|
}
|
|
255
|
+
const unsatisfiedStatuses = Object.values(statuses).filter((status) => !['satisfied', 'not_applicable'].includes(status));
|
|
256
|
+
if (bundle?.result?.claim_status === 'passed' && unsatisfiedStatuses.length > 0) {
|
|
257
|
+
addError(errors, 'inconsistent_claim_status', 'result.claim_status', 'UI proof claim_status cannot be passed when comparison statuses are unsatisfied.', 'Use partial, failed, waived, deferred, or not_applicable when any slot comparison is not satisfied.');
|
|
258
|
+
}
|
|
205
259
|
}
|
|
206
260
|
|
|
207
261
|
function validateClaimLimits(bundle, errors) {
|
|
@@ -234,7 +288,7 @@ function isSanitizedSensitivity(value) {
|
|
|
234
288
|
return typeof value === 'string' && /(^|[_\s-])(sanitized|public_safe|public-safe)($|[_\s-])/.test(value.toLowerCase());
|
|
235
289
|
}
|
|
236
290
|
|
|
237
|
-
function validateArtifacts(bundle, errors, publicClaim) {
|
|
291
|
+
function validateArtifacts(bundle, errors, publicClaim, options = {}) {
|
|
238
292
|
const artifacts = normalizeArray(bundle?.artifacts);
|
|
239
293
|
if (artifacts.length === 0) {
|
|
240
294
|
addError(errors, 'missing_artifacts', 'artifacts', 'Missing UI proof artifacts list.', 'Record artifact metadata for each referenced proof artifact.');
|
|
@@ -254,6 +308,12 @@ function validateArtifacts(bundle, errors, publicClaim) {
|
|
|
254
308
|
} else {
|
|
255
309
|
validateArtifactReferenceSafety(ref, artifactPath, errors);
|
|
256
310
|
artifactRefs.add(ref);
|
|
311
|
+
if (options.requireLocalArtifactExists && !/^https?:\/\//i.test(ref) && hasValue(options.workspaceRoot)) {
|
|
312
|
+
const artifactFile = resolve(options.workspaceRoot, ref);
|
|
313
|
+
if (!existsSync(artifactFile) || statSync(artifactFile).isDirectory()) {
|
|
314
|
+
addError(errors, 'missing_local_artifact', artifactPath, `UI proof artifact file does not exist: ${ref}`, 'Create the referenced artifact, correct the path, or narrow the proof claim.');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
257
317
|
}
|
|
258
318
|
for (const field of REQUIRED_ARTIFACT_FIELDS) {
|
|
259
319
|
requireField(artifact, field, artifactPath, errors);
|
|
@@ -277,6 +337,40 @@ function validateArtifacts(bundle, errors, publicClaim) {
|
|
|
277
337
|
return artifactRefs;
|
|
278
338
|
}
|
|
279
339
|
|
|
340
|
+
export function validateUiProofSlots(slots) {
|
|
341
|
+
const errors = [];
|
|
342
|
+
const normalizedSlots = normalizeArray(slots);
|
|
343
|
+
if (normalizedSlots.length === 0) {
|
|
344
|
+
addError(errors, 'missing_planned_slots', 'ui_proof_slots', 'Planned UI proof input must include at least one slot.', 'Provide ui_proof_slots or no_ui_proof_rationale for non-UI work.');
|
|
345
|
+
return { valid: false, errors, warnings: [] };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
for (const [index, slot] of normalizedSlots.entries()) {
|
|
349
|
+
const slotPath = `ui_proof_slots[${index}]`;
|
|
350
|
+
if (!isPlainObject(slot)) {
|
|
351
|
+
addError(errors, 'invalid_planned_slot', slotPath, 'Planned UI proof slot must be an object.', 'Record a scoped slot with claim, route_state, viewport, evidence, artifacts, validation, and claim limit.');
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
for (const field of REQUIRED_SLOT_FIELDS) requireField(slot, field, slotPath, errors);
|
|
355
|
+
for (const [kindIndex, kind] of normalizeArray(slot.required_evidence_kinds || slot.requiredEvidenceKinds).entries()) {
|
|
356
|
+
if (!EVIDENCE_KINDS.includes(kind)) {
|
|
357
|
+
addError(errors, 'unsupported_planned_evidence_kind', `${slotPath}.required_evidence_kinds[${kindIndex}]`, `Unsupported planned UI proof evidence kind: ${kind}`, `Use only: ${EVIDENCE_KINDS.join(', ')}.`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (hasValue(slot.manual_acceptance_required) && typeof slot.manual_acceptance_required !== 'boolean') {
|
|
361
|
+
addError(errors, 'invalid_manual_acceptance_required', `${slotPath}.manual_acceptance_required`, 'manual_acceptance_required must be a boolean.', 'Use true only when human judgment is required for this slot; otherwise use false.');
|
|
362
|
+
}
|
|
363
|
+
if (normalizeArray(slot.minimum_observations || slot.minimumObservations).length === 0) {
|
|
364
|
+
addError(errors, 'missing_minimum_observations', `${slotPath}.minimum_observations`, 'Planned UI proof slot must include minimum observations.', 'List the observations execution must prove for this slot.');
|
|
365
|
+
}
|
|
366
|
+
if (normalizeArray(slot.expected_artifact_types || slot.expectedArtifactTypes).length === 0) {
|
|
367
|
+
addError(errors, 'missing_expected_artifact_types', `${slotPath}.expected_artifact_types`, 'Planned UI proof slot must include expected artifact types.', 'List expected artifact types such as screenshot, trace, report, or dom_snapshot.');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return { valid: errors.length === 0, errors, warnings: [] };
|
|
372
|
+
}
|
|
373
|
+
|
|
280
374
|
function validatePrivacy(bundle, errors, publicClaim) {
|
|
281
375
|
validateObservationPrivacy(bundle.privacy, 'privacy', errors);
|
|
282
376
|
if (publicClaim && bundle.privacy?.raw_artifacts_safe_to_publish !== true) {
|
|
@@ -539,15 +633,30 @@ function compareSlotToBundle(slot, slotIdValue, observed) {
|
|
|
539
633
|
}
|
|
540
634
|
}
|
|
541
635
|
|
|
636
|
+
const expectedArtifactTypes = normalizeArray(slot?.expected_artifact_types || slot?.expectedArtifactTypes);
|
|
637
|
+
const observedArtifactTypes = new Set(normalizeArray(bundle?.artifacts).filter(isPlainObject).flatMap((artifact) => [
|
|
638
|
+
typeof artifact.type === 'string' ? artifact.type.toLowerCase() : '',
|
|
639
|
+
artifactType(artifact),
|
|
640
|
+
]).filter(Boolean));
|
|
641
|
+
const missingArtifactTypes = expectedArtifactTypes.filter((type) => !observedArtifactTypes.has(type));
|
|
642
|
+
if (missingArtifactTypes.length > 0) {
|
|
643
|
+
issues.push({
|
|
644
|
+
code: 'missing_expected_artifact_type',
|
|
645
|
+
path: 'artifacts[].type',
|
|
646
|
+
message: `Observed UI proof for slot ${slotIdValue} is missing expected artifact type(s): ${missingArtifactTypes.join(', ')}.`,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
542
650
|
const status = issues.length === 0 ? 'satisfied' : (bundleStatus === 'missing' ? 'missing' : 'partial');
|
|
543
651
|
return { status, issues, source: observed.source };
|
|
544
652
|
}
|
|
545
653
|
|
|
546
654
|
export function compareUiProofSlots(plannedSlots, observedBundles) {
|
|
547
655
|
const slots = normalizeArray(plannedSlots);
|
|
656
|
+
const slotValidation = validateUiProofSlots(slots);
|
|
548
657
|
const bundles = normalizeArray(observedBundles).map(normalizeObservedBundle);
|
|
549
658
|
const results = [];
|
|
550
|
-
const errors = [];
|
|
659
|
+
const errors = [...slotValidation.errors];
|
|
551
660
|
|
|
552
661
|
for (const observed of bundles) {
|
|
553
662
|
if (!observed.validation.valid) {
|
|
@@ -614,14 +723,16 @@ export function validateUiProofBundle(bundle, options = {}) {
|
|
|
614
723
|
const publicClaim = hasPublicClaim(bundle, options);
|
|
615
724
|
validateClaimUses(bundle, options, errors);
|
|
616
725
|
validateEvidenceKinds(bundle, errors);
|
|
726
|
+
validateToolsUsed(bundle, errors);
|
|
617
727
|
validateCommandsOrManualSteps(bundle, errors);
|
|
618
728
|
validateObservations(bundle, errors);
|
|
619
729
|
validateResult(bundle, errors);
|
|
730
|
+
validateFailureClassification(bundle, errors);
|
|
620
731
|
validateComparisonStatuses(bundle, errors);
|
|
621
732
|
validateClaimLimits(bundle, errors);
|
|
622
733
|
validatePrivacy(bundle, errors, publicClaim);
|
|
623
734
|
validatePublicObservationPrivacy(bundle, errors, publicClaim);
|
|
624
|
-
const artifactRefs = validateArtifacts(bundle, errors, publicClaim);
|
|
735
|
+
const artifactRefs = validateArtifacts(bundle, errors, publicClaim, options);
|
|
625
736
|
validateObservationArtifactRefs(bundle, artifactRefs, errors);
|
|
626
737
|
|
|
627
738
|
return { valid: errors.length === 0, errors, warnings };
|
|
@@ -688,7 +799,14 @@ export function parseUiProofSlotsContent(content, filePath = 'UI proof slots') {
|
|
|
688
799
|
};
|
|
689
800
|
}
|
|
690
801
|
|
|
691
|
-
|
|
802
|
+
const validation = validateUiProofSlots(slots);
|
|
803
|
+
return {
|
|
804
|
+
slots,
|
|
805
|
+
errors: validation.errors.map((error) => ({
|
|
806
|
+
...error,
|
|
807
|
+
path: error.path === 'ui_proof_slots' ? filePath : `${filePath}.${error.path}`,
|
|
808
|
+
})),
|
|
809
|
+
};
|
|
692
810
|
}
|
|
693
811
|
|
|
694
812
|
export function readUiProofBundleFile(filePath) {
|
|
@@ -763,7 +881,12 @@ function cmdValidate(cwd, args) {
|
|
|
763
881
|
const parsed = readUiProofBundleFile(target);
|
|
764
882
|
const validation = parsed.errors.length > 0
|
|
765
883
|
? { valid: false, errors: parsed.errors, warnings: [] }
|
|
766
|
-
: validateUiProofBundle(parsed.bundle, {
|
|
884
|
+
: validateUiProofBundle(parsed.bundle, {
|
|
885
|
+
claimUses: parseClaimUse(flags),
|
|
886
|
+
requireLocalArtifactExists: true,
|
|
887
|
+
workspaceRoot: cwd,
|
|
888
|
+
bundleDir: dirname(target),
|
|
889
|
+
});
|
|
767
890
|
|
|
768
891
|
output({ operation: 'ui-proof validate', target: targetArg, valid: validation.valid, errors: validation.errors, warnings: validation.warnings });
|
|
769
892
|
if (!validation.valid) process.exitCode = 1;
|
|
@@ -799,7 +922,7 @@ function cmdCompare(cwd, args) {
|
|
|
799
922
|
}
|
|
800
923
|
|
|
801
924
|
const comparison = planned.errors.length > 0
|
|
802
|
-
? { status: '
|
|
925
|
+
? { status: 'partial', slots: [], errors: planned.errors }
|
|
803
926
|
: compareUiProofSlots(planned.slots, observedBundles);
|
|
804
927
|
|
|
805
928
|
output({
|
package/distilled/DESIGN.md
CHANGED
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
60. [Release Closeout Contract](#d60---release-closeout-contract)
|
|
74
74
|
61. [Deliberate Subagent Contract](#d61---deliberate-subagent-contract)
|
|
75
75
|
62. [Repo-Native UI Proof Contract](#d62---repo-native-ui-proof-contract)
|
|
76
|
+
63. [Computed-First Control Map](#d63---computed-first-control-map)
|
|
76
77
|
|
|
77
78
|
---
|
|
78
79
|
|
|
@@ -2817,30 +2818,36 @@ Posture compatibility is part of that closeout contract: `repo_closeout` and `ru
|
|
|
2817
2818
|
|
|
2818
2819
|
## D62 - Repo-Native UI Proof Contract
|
|
2819
2820
|
|
|
2820
|
-
**Decision (2026-04-28):** UI-sensitive work should carry a compact planned proof-slot contract and, when executed, an observed UI proof bundle that references artifacts by path or link while preserving the existing closure evidence kinds: `code`, `test`, `runtime`, `delivery`, and `human`.
|
|
2821
|
+
**Decision (2026-04-28; revised 2026-05-08):** UI-sensitive work should carry a compact planned proof-slot contract and, when executed, an observed UI proof bundle that references artifacts by path or link while preserving the existing closure evidence kinds: `code`, `test`, `runtime`, `delivery`, and `human`. For live rendered UI proof, `agent-browser` is the default runtime evidence path for consumers, while existing Playwright tests remain the canonical repeatable browser-regression path when present. The deterministic `ui-proof` validator remains provider-agnostic structural validation, but it now validates planned slot specificity, concise tool provenance, local artifact path existence when validating from files, raw-artifact safety for paths and URLs, and failed/partial proof classification so the workflow cannot degrade back into unstructured "looks good" review.
|
|
2821
2822
|
|
|
2822
2823
|
**Context:**
|
|
2823
2824
|
- UI proof targets the recurring failure mode where agents claim a UI works or looks good without rendered proof, matched observations, or explicit human judgment.
|
|
2824
2825
|
- The contract defines proof slots, proof bundles, comparison statuses, fail-closed agent guardrails, deterministic metadata validation, privacy metadata, and health visibility without adding a browser-provider framework.
|
|
2825
2826
|
- GSD's archived planner, executor, and verifier roles preserve strong lifecycle discipline, but they do not provide this UI-specific planned-vs-observed proof model. GSDD keeps the lifecycle leverage and adds a repo-native UI proof substrate without adding a browser-provider framework.
|
|
2827
|
+
- OneShot's QC guidance and Vercel's `agent-browser` skill converge on an interactive browser loop for snapshots, ref-based interaction, screenshots, and network/console-adjacent inspection. GSDD adapts that as a default workflow instruction, not as a hard validator dependency.
|
|
2826
2828
|
|
|
2827
2829
|
**Decision:**
|
|
2828
2830
|
- Planning must classify UI-sensitive work and require either `ui_proof_slots` or an explicit `no_ui_proof_rationale`.
|
|
2829
|
-
- Planned slots record claim, route/state, required evidence kinds, minimum observations, environment/viewport, manual-acceptance requirement, claim limit, and requirement IDs.
|
|
2831
|
+
- Planned slots record claim, route/state, required evidence kinds, minimum observations, expected artifact types, runnable validation command, environment/viewport, manual-acceptance requirement, claim limit, and requirement IDs.
|
|
2830
2832
|
- Observed proof bundles record claim, requirement/slot IDs, route/state, environment, viewport, evidence inputs, commands/manual steps, observations, artifacts, privacy metadata, result, and claim limits.
|
|
2833
|
+
- Planned slots must be tight enough for the plan checker to reject vague proof: specific route/state, viewport rationale or narrowed claim limit, minimum observations, expected artifact types, runnable validation, and matchability back to the exact UI claim.
|
|
2834
|
+
- The planner chooses viewport coverage, but responsive or layout-sensitive claims require desktop/mobile or equivalent state coverage unless the claim is explicitly narrowed.
|
|
2835
|
+
- Execution defaults to `agent-browser` for live UI runtime proof: open the route/state, capture interactive snapshots/refs where relevant, exercise the changed flow, capture screenshots for planned viewport(s), and record relevant console/network observations.
|
|
2836
|
+
- Existing Playwright tests or package scripts remain the canonical repeatable browser-regression evidence when present. Playwright scripting is reserved for checks `agent-browser` cannot cover cleanly, such as JS-disabled behavior, structured console listeners, or multi-context testing.
|
|
2831
2837
|
- Verification compares planned slots to observed bundles using `satisfied`, `partial`, `missing`, `waived`, `deferred`, and `not_applicable`; waiver and deferral are not proof.
|
|
2832
2838
|
- UI correctness claims fail closed unless rendered proof is matched exactly to claim, route/state, observation, evidence kind, artifact path or manual step, privacy metadata, result, and claim limit, or an explicit waiver/deferment narrows the claim.
|
|
2833
2839
|
- Human acceptance may close a narrowed claim and record proof debt, but it must not convert missing or mismatched non-human evidence into `satisfied` proof.
|
|
2834
2840
|
- Screenshots, traces, videos, reports, accessibility scans, Gherkin, and visual diffs are artifact types or activities mapped onto the five existing evidence kinds, not new evidence kinds.
|
|
2835
2841
|
- Source annotations, AST/cAST findings, semantic search hits, comments, and Semble-like retrieval may discover proof obligations, but they are discovery hints only and do not satisfy proof slots.
|
|
2836
2842
|
- Visual taste, accessibility judgment, baseline acceptance, subjective polish/layout quality, and privacy publication require human evidence or explicit waiver, and human approval does not replace required `code`, `test`, `runtime`, or `delivery` evidence.
|
|
2837
|
-
- Deterministic
|
|
2838
|
-
- `gsdd health` reports invalid known UI proof bundles as E10 using the same validator, staying read-only and
|
|
2843
|
+
- Deterministic validation keeps the evidence and comparison-status vocabularies unchanged: planned slots require specific claim, route/state, evidence, expected artifacts, validation, viewport, and claim-limit fields; artifact entries require `visibility`, `retention`, `sensitivity`, and `safe_to_publish`; raw screenshots, traces, videos, DOM snapshots, and reports default to `local_only` plus `safe_to_publish: false`; `bin/lib/ui-proof.mjs` validates required bundle/observation fields, structured command/manual-step entries, fixed evidence kinds, concise `tools_used` IDs, claim/result statuses, comparison statuses, failure classification for failed/partial proof, claim limits, privacy metadata, safe artifact references, local artifact path existence when validating file-backed bundles, and public/tracked/delivery proof claims backed by local-only, unsafe, unsanitized, or privacy-contradictory artifacts.
|
|
2844
|
+
- `gsdd health` reports invalid known UI proof bundles as E10 using the same validator, staying read-only and avoiding raw artifact content inspection.
|
|
2845
|
+
- Failed UI proof is reported through existing GSDD gap/proof-debt language. Product behavior defects, missing or blocked infrastructure, flaky harnesses, and ambiguous specs explain causes, but they do not add new evidence kinds, result statuses, or comparison statuses.
|
|
2839
2846
|
|
|
2840
2847
|
**Leverage:**
|
|
2841
|
-
- Lost: UI-sensitive work now carries a small proof-contract burden, and
|
|
2842
|
-
- Kept: repo-native markdown artifacts, optional project tooling, fixed closure evidence kinds, generated-surface freshness,
|
|
2843
|
-
- Gained: exact claim-to-proof traceability, strict comparison statuses, privacy and claim-limit metadata, fail-closed overclaim guardrails, deterministic metadata validation, and health-visible protection against unsafe public proof claims.
|
|
2848
|
+
- Lost: UI-sensitive work now carries a small proof-contract burden, and default live proof guidance adds slightly more specificity for planners/checkers to enforce.
|
|
2849
|
+
- Kept: repo-native markdown artifacts, optional project tooling, fixed closure evidence kinds, generated-surface freshness, the plan/execute/verify separation, and provider-agnostic deterministic metadata validation.
|
|
2850
|
+
- Gained: exact claim-to-proof traceability, strict comparison statuses, privacy and claim-limit metadata, fail-closed overclaim guardrails, deterministic metadata validation, a concrete live browser evidence path, and health-visible protection against unsafe public proof claims.
|
|
2844
2851
|
|
|
2845
2852
|
**Evidence:**
|
|
2846
2853
|
- `distilled/templates/ui-proof.md`
|
|
@@ -2849,11 +2856,60 @@ Posture compatibility is part of that closeout contract: `repo_closeout` and `ru
|
|
|
2849
2856
|
- `bin/lib/templates.mjs`, `bin/lib/ui-proof.mjs`, `bin/lib/health.mjs`, `bin/lib/rendering.mjs`
|
|
2850
2857
|
- `tests/phase.test.cjs`, `tests/gsdd.guards.test.cjs`, `tests/gsdd.health.test.cjs`, `tests/gsdd.init.test.cjs`
|
|
2851
2858
|
- GSD comparison: the upstream planner, executor, and verifier role patterns preserve lifecycle rigor, but they do not define UI proof slots or planned-vs-observed UI proof bundles.
|
|
2859
|
+
- OneShot QC source: `https://github.com/oneshot-repo/OneShot/tree/main/skills`
|
|
2860
|
+
- Vercel `agent-browser` docs: `https://github.com/vercel-labs/agent-browser/blob/main/skill-data/core/SKILL.md` and `https://agent-browser.dev/snapshots`
|
|
2861
|
+
- Playwright docs: `https://playwright.dev/docs/trace-viewer`, `https://playwright.dev/docs/next/screenshots`, and `https://playwright.dev/mcp/tools/tracing`
|
|
2862
|
+
- Chrome DevTools for agents/MCP docs: `https://developer.chrome.com/docs/devtools/agents` and `https://developer.chrome.com/blog/chrome-devtools-mcp?hl=en`
|
|
2863
|
+
- Harness and UI-agent pitfall sources: `https://www.anthropic.com/engineering/harness-design-long-running-apps`, `https://www.huuhka.net/browser-verification-for-coding-agents-chrome-devtools-mcp-vs-agent-browser/`, `https://www.developersdigest.tech/blog/long-running-agents-need-harnesses`, `https://codemyspec.com/blog/agentic-qa-verification`, `https://dev.to/ratikkoka/your-ui-is-invisible-to-ai-agents-heres-how-to-fix-it-1ib3`, `https://dev.to/louaiboumediene/the-ai-harness-why-your-ai-coding-agent-is-only-as-smart-as-the-repo-you-put-it-in-cml`, and `https://tessl.io/blog/webmcp-making-web-apps-faster-and-cheaper-for-ai-agents/`
|
|
2864
|
+
- OpenSpec docs: `https://openspec.dev/`
|
|
2865
|
+
- LeanSpec docs: `https://www.lean-spec.dev/docs/guide/first-principles`
|
|
2866
|
+
- OpenAI Codex docs: `https://help.openai.com/en/articles/11369540-codex-in-chatgpt`
|
|
2867
|
+
- Anthropic Agent Skills docs: `https://docs.claude.com/en/docs/agents-and-tools/agent-skills`
|
|
2868
|
+
- GitHub Copilot customization docs: `https://docs.github.com/en/copilot/concepts/prompting/response-customization`
|
|
2852
2869
|
|
|
2853
2870
|
**Consequences:**
|
|
2854
2871
|
- Future UI-related phases must not add new evidence kinds by treating artifact types as proof categories.
|
|
2855
2872
|
- Future dogfood or runtime validation must not upgrade artifact counts or human waivers into proof.
|
|
2856
2873
|
- Generated runtime surfaces and local templates must stay freshness-checkable through `gsdd update --templates` and health diagnostics.
|
|
2874
|
+
- Future provider/tooling work must not make `agent-browser` a required validator field without a separate product decision; the current contract makes it the default workflow path, not a schema lock.
|
|
2875
|
+
|
|
2876
|
+
## D63 - Computed-First Control Map
|
|
2877
|
+
|
|
2878
|
+
**Decision (2026-05-08):** Long-running multi-agent and multi-worktree control uses a computed-first `gsdd control-map` helper rather than a new lifecycle workflow or a vendor session parser. The helper computes repo/worktree/planning truth live and overlays optional local annotations only for intent that git cannot know.
|
|
2879
|
+
|
|
2880
|
+
**Context:**
|
|
2881
|
+
- Gap I52 showed that ordinary `git status` can be clean while sibling worktrees, detached runtime worktrees, ignored/generated surfaces, snapshots, dirty local WIP, and cleanup obligations remain unexplained.
|
|
2882
|
+
- Gap I54 showed that repeated subagent swarms can become a substitute for shared state when there is no one-screen control map for active branches, ownership, proof state, and cleanup debt.
|
|
2883
|
+
- Current runtime research shows the only portable cross-vendor coordination layer is repo artifacts plus generated workflow entrypoints. Claude, OpenCode, Codex, Cursor, Copilot, and Gemini do not expose one uniform authoritative session/worktree store.
|
|
2884
|
+
- Current harness guidance favors structured handoff artifacts, worktree isolation, evaluator loops, approval gates around side effects, and browser/runtime evidence. Those ideas fit Workspine only if repo truth remains primary and vendor adapters stay thin.
|
|
2885
|
+
|
|
2886
|
+
**Decision:**
|
|
2887
|
+
- Add `gsdd control-map [--json] [--with-ignored] [--annotations <path>]` to the main CLI and generated `.planning/bin/gsdd.mjs` helper runtime.
|
|
2888
|
+
- Compute authority from live git/worktree state first: canonical checkout, branch, HEAD, upstream divergence when comparable, tracked/untracked dirty buckets, optional ignored-path scans through `--with-ignored`, sibling git worktrees, detached/bare state, invalid git access, planning drift, checkpoint existence, lifecycle state, and repo-local runtime worktree directories.
|
|
2889
|
+
- Read optional annotations from `.planning/.local/control-map.annotations.json`. Annotations may record `runtime_owner`, intended scope, write set, cleanup state, proof state, next step, branch, and last known head.
|
|
2890
|
+
- Treat annotations as stale-checkable intent only. They never outrank repo truth, planning artifacts, or checkpoint reconciliation.
|
|
2891
|
+
- Keep transcript/session stores out of the helper. Vendor session evidence may support postmortems, but it is not live product truth.
|
|
2892
|
+
- Wire the control map into portable workflow behavior by having `progress`, `resume`, `pause`, `quick`, `plan`, and `execute` consult it when available. This is guidance plus deterministic helper output, not a new workflow lane.
|
|
2893
|
+
|
|
2894
|
+
**Leverage:**
|
|
2895
|
+
- Lost: a pure zero-file model cannot preserve non-computable intent such as owner/runtime, intended scope, and cleanup obligation.
|
|
2896
|
+
- Kept: Workspine remains a lightweight repo-native spine; no new lifecycle workflow, no dashboard/control plane, no vendor session authority, and no change to the five evidence kinds.
|
|
2897
|
+
- Gained: agents can explain "clean" precisely across tracked, untracked, sibling, detached, stale, and annotated state by default, and across ignored/generated local surfaces when the caller requests the explicit `--with-ignored` scan before planning, execution, resume, cleanup, or milestone continuation.
|
|
2898
|
+
|
|
2899
|
+
**Evidence:**
|
|
2900
|
+
- `bin/lib/control-map.mjs`, `bin/gsdd.mjs`, `bin/lib/rendering.mjs`
|
|
2901
|
+
- `distilled/workflows/progress.md`, `resume.md`, `pause.md`, `quick.md`, `plan.md`, `execute.md`
|
|
2902
|
+
- `tests/gsdd.control-map.test.cjs`
|
|
2903
|
+
- `.internal-research/gaps.md` Gap I52 and Gap I54
|
|
2904
|
+
- `.internal-research/lessons-learned.md` entries on multi-worktree registry, clean-vs-editor-visible noise, checkpoint/worktree truth split, and subagent stop conditions
|
|
2905
|
+
- GSD comparison: upstream GSD preserves lifecycle rigor but does not define a vendor-agnostic computed worktree/control-map helper.
|
|
2906
|
+
- OpenSpec comparison: OpenSpec optimizes change-level speed and archive flow, but does not own long-running multi-worktree local-state reconciliation as a portable harness surface.
|
|
2907
|
+
- Harness sources: `https://www.anthropic.com/engineering/harness-design-long-running-apps`, `https://code.claude.com/docs/en/worktrees`, `https://developers.openai.com/codex/cloud`, `https://developers.openai.com/api/docs/guides/agents/orchestration`, `https://developers.openai.com/api/docs/guides/agents/guardrails-approvals`, `https://developers.openai.com/api/docs/guides/agent-evals`, `https://docs.github.com/en/copilot/concepts/agents/cloud-agent/about-cloud-agent`, `https://agent-browser.dev/sessions`, and `https://developer.chrome.com/docs/devtools/agents`.
|
|
2908
|
+
|
|
2909
|
+
**Consequences:**
|
|
2910
|
+
- Future cleanup, resume, and parallel-worktree work should start from `gsdd control-map --json` rather than repeated ad hoc repo audits; use `--with-ignored` before making a clean-workspace claim that includes ignored or generated surfaces.
|
|
2911
|
+
- A future mutation command may update annotations, but the current helper intentionally stays computed/read-first and safe to call from status surfaces.
|
|
2912
|
+
- Future health or preflight hardening can consume the same helper output for stricter blocking, but must avoid turning local annotations into product truth.
|
|
2857
2913
|
|
|
2858
2914
|
---
|
|
2859
2915
|
|
|
@@ -491,6 +491,29 @@
|
|
|
491
491
|
- `agents/planner.md`, `agents/executor.md`, `agents/verifier.md`, `distilled/templates/delegates/plan-checker.md`
|
|
492
492
|
- `bin/lib/templates.mjs`, `bin/lib/ui-proof.mjs`, `bin/lib/health.mjs`, `bin/lib/rendering.mjs`
|
|
493
493
|
- `tests/phase.test.cjs`, `tests/gsdd.guards.test.cjs`, `tests/gsdd.health.test.cjs`, `tests/gsdd.init.test.cjs`
|
|
494
|
+
- OneShot QC/browser policy: https://github.com/oneshot-repo/OneShot/tree/main/skills
|
|
495
|
+
- Vercel agent-browser docs: https://github.com/vercel-labs/agent-browser/blob/main/skill-data/core/SKILL.md, https://agent-browser.dev/snapshots
|
|
496
|
+
- Playwright browser proof docs: https://playwright.dev/docs/trace-viewer, https://playwright.dev/docs/next/screenshots, https://playwright.dev/mcp/tools/tracing
|
|
497
|
+
- Chrome DevTools agent/browser docs: https://developer.chrome.com/docs/devtools/agents, https://developer.chrome.com/blog/chrome-devtools-mcp?hl=en
|
|
498
|
+
- Harness engineering source: https://www.anthropic.com/engineering/harness-design-long-running-apps
|
|
499
|
+
- UI-agent browser verification source: https://www.huuhka.net/browser-verification-for-coding-agents-chrome-devtools-mcp-vs-agent-browser/
|
|
500
|
+
- Agent harness discipline source: https://www.developersdigest.tech/blog/long-running-agents-need-harnesses
|
|
501
|
+
- Agentic QA verification-gap source: https://codemyspec.com/blog/agentic-qa-verification
|
|
502
|
+
- Agent-hostile UI semantics source: https://dev.to/ratikkoka/your-ui-is-invisible-to-ai-agents-heres-how-to-fix-it-1ib3
|
|
503
|
+
- Browser-harness efficiency source: https://dev.to/louaiboumediene/the-ai-harness-why-your-ai-coding-agent-is-only-as-smart-as-the-repo-you-put-it-in-cml
|
|
504
|
+
- Dynamic-UI harness brittleness source: https://tessl.io/blog/webmcp-making-web-apps-faster-and-cheaper-for-ai-agents/
|
|
505
|
+
- Long-term pitfalls carried forward: do not accept screenshot-free "looks good" claims, weak planned slots, unverified artifact paths, stale interactive refs after page mutation, partial/failed proof without failure classification, raw artifact publication without privacy metadata, browser contention in parallel checks, or semantic/selector-poor UI that forces fragile coordinate inspection.
|
|
506
|
+
- Supporting spec/runtime docs: https://openspec.dev/, https://www.lean-spec.dev/docs/guide/first-principles, https://help.openai.com/en/articles/11369540-codex-in-chatgpt, https://docs.claude.com/en/docs/agents-and-tools/agent-skills, https://docs.github.com/en/copilot/concepts/prompting/response-customization
|
|
507
|
+
|
|
508
|
+
## D63 — Computed-First Control Map
|
|
509
|
+
- `bin/lib/control-map.mjs`, `bin/gsdd.mjs`, `bin/lib/rendering.mjs`
|
|
510
|
+
- `distilled/workflows/progress.md`, `resume.md`, `pause.md`, `quick.md`, `plan.md`, `execute.md`
|
|
511
|
+
- `tests/gsdd.control-map.test.cjs`
|
|
512
|
+
- `.internal-research/gaps.md` Gap I52 and Gap I54
|
|
513
|
+
- `.internal-research/lessons-learned.md` multi-worktree registry, clean-vs-editor-visible noise, checkpoint/worktree truth split, and subagent stop-condition lessons
|
|
514
|
+
- GSD comparison: upstream GSD lifecycle rigor does not include a vendor-agnostic computed worktree/control-map helper
|
|
515
|
+
- OpenSpec comparison: OpenSpec change archive flow does not own long-running multi-worktree local-state reconciliation as a portable harness surface
|
|
516
|
+
- Harness sources: https://www.anthropic.com/engineering/harness-design-long-running-apps, https://code.claude.com/docs/en/worktrees, https://developers.openai.com/codex/cloud, https://developers.openai.com/api/docs/guides/agents/orchestration, https://developers.openai.com/api/docs/guides/agents/guardrails-approvals, https://developers.openai.com/api/docs/guides/agent-evals, https://docs.github.com/en/copilot/concepts/agents/cloud-agent/about-cloud-agent, https://agent-browser.dev/sessions, https://developer.chrome.com/docs/devtools/agents
|
|
494
517
|
|
|
495
518
|
---
|
|
496
519
|
|
package/distilled/README.md
CHANGED
|
@@ -71,6 +71,14 @@ npx -y gsdd-cli init -> bootstrap (create .planning/, copy templates, gene
|
|
|
71
71
|
/gsdd-progress -> show status, route to next action
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
The main operator spine is four workflow moves after bootstrap: `new-project -> plan -> execute -> verify`. The other public workflow surfaces are support lanes for milestone closeout, quick work, progress, pause/resume, and brownfield orientation.
|
|
75
|
+
|
|
76
|
+
Helper command for long-running sessions:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
npx -y gsdd-cli control-map [--json] [--with-ignored] -> computed repo/worktree/planning state plus local annotations
|
|
80
|
+
```
|
|
81
|
+
|
|
74
82
|
## Brownfield Entry Contract
|
|
75
83
|
|
|
76
84
|
Use the same three-way routing everywhere:
|
|
@@ -101,6 +109,7 @@ Use the same three-way routing everywhere:
|
|
|
101
109
|
Architecture notes:
|
|
102
110
|
- `bin/gsdd.mjs` remains the thin generator entrypoint, while vendor-specific rendering lives in adapter modules.
|
|
103
111
|
- Codex CLI uses the always-generated `.agents/skills/gsdd-*` surface as its entry path, relies on `.planning/bin/gsdd.mjs` for deterministic helper calls, and can add a native `.codex/agents/gsdd-plan-checker.toml` checker agent.
|
|
112
|
+
- `control-map` is a helper command, not a lifecycle workflow: it computes repo/worktree/planning truth first and treats `.planning/.local/` annotations as local intent only.
|
|
104
113
|
- Codex VS Code/app are separate surfaces from Codex CLI; do not claim the CLI proof for them unless they expose compatible skill discovery. Fallback is opening or pasting the generated `SKILL.md`.
|
|
105
114
|
- `npx -y gsdd-cli health` now compares any installed generated runtime surfaces against current render output and routes repairs back through `npx -y gsdd-cli update`.
|
|
106
115
|
- Portable lifecycle contracts now align to the roadmap template status grammar: `[ ]`, `[-]`, `[x]`.
|
|
@@ -35,6 +35,7 @@ Verify these dimensions:
|
|
|
35
35
|
- `escalation_integrity`: tasks include checkpoints or escalation when evidence, permissions, user decisions, or risky ambiguity are required.
|
|
36
36
|
- `closure_honesty`: the plan's done criteria and evidence limits support only claims that execution can actually prove.
|
|
37
37
|
- `closure_honesty`: for UI proof, reject agent-only `looks good` closure, artifact-count proof, unsupported evidence kinds, and human acceptance that converts missing/mismatched non-human evidence into `satisfied` proof. Waiver, deferment, proof debt, or narrowed-claim language is acceptable only when the stronger UI claim is not treated as proven.
|
|
38
|
+
- `closure_honesty`: for UI proof planning, reject weak slots that omit a specific route/state, viewport rationale or narrowed viewport claim limit, minimum observations, expected artifact types, runnable validation, or a way to compare observed proof back to the planned claim. Treat under-specified viewport coverage as a blocker for responsive or layout-sensitive claims. `agent-browser` is the default live runtime evidence path; do not block a slot solely for using another project-native browser path, but require the plan to explain the `agent-browser` availability constraint and fallback choice.
|
|
38
39
|
- `closure_honesty`: for UI proof privacy, require artifact `visibility`, `retention`, `sensitivity`, and `safe_to_publish`, require `gsdd ui-proof validate` or `gsdd health` when bundle metadata exists, and reject public/tracked/delivery/publication proof claims backed by local-only or `safe_to_publish: false` artifacts.
|
|
39
40
|
- `high_leverage_review`: high-leverage surfaces have a second-pass review or equivalent contradiction/staleness check before completion.
|
|
40
41
|
- `approach_alignment`: when APPROACH.md is provided, verify that plan tasks implement the chosen approaches from the user's decisions. Check:
|