@sun-asterisk/sungen 3.3.0-alpha.1 → 3.3.0-alpha.2
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/capabilities/discover.d.ts +4 -0
- package/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +16 -2
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/cli/commands/audit.js +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/contract-derive-command.d.ts +3 -2
- package/dist/cli/commands/contract-derive-command.d.ts.map +1 -1
- package/dist/cli/commands/contract-derive-command.js +3 -3
- package/dist/cli/commands/contract-derive-command.js.map +1 -1
- package/dist/cli/commands/contract.js +1 -1
- package/dist/cli/commands/contract.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +9 -2
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/journey.js +2 -2
- package/dist/cli/commands/journey.js.map +1 -1
- package/dist/cli/commands/next.d.ts.map +1 -1
- package/dist/cli/commands/next.js +8 -6
- package/dist/cli/commands/next.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/project-sungen-invocation.d.ts +7 -0
- package/dist/cli/project-sungen-invocation.d.ts.map +1 -0
- package/dist/cli/project-sungen-invocation.js +76 -0
- package/dist/cli/project-sungen-invocation.js.map +1 -0
- package/dist/contract/derive-free-feature.d.ts +8 -0
- package/dist/contract/derive-free-feature.d.ts.map +1 -1
- package/dist/contract/derive-free-feature.js +10 -1
- package/dist/contract/derive-free-feature.js.map +1 -1
- package/dist/contract/trace/collect-trace-evidence.js +1 -1
- package/dist/contract/trace/collect-trace-evidence.js.map +1 -1
- package/dist/contract/trace/test-step-join.d.ts +3 -3
- package/dist/contract/trace/test-step-join.d.ts.map +1 -1
- package/dist/contract/trace/test-step-join.js +2 -2
- package/dist/contract/trace/test-step-join.js.map +1 -1
- package/dist/contract/trace/trace-file-reader.d.ts +7 -1
- package/dist/contract/trace/trace-file-reader.d.ts.map +1 -1
- package/dist/contract/trace/trace-file-reader.js +10 -4
- package/dist/contract/trace/trace-file-reader.js.map +1 -1
- package/dist/contract/trace/trace-run-record.d.ts +4 -1
- package/dist/contract/trace/trace-run-record.d.ts.map +1 -1
- package/dist/contract/trace/trace-run-record.js +20 -14
- package/dist/contract/trace/trace-run-record.js.map +1 -1
- package/dist/contract/trace/trace-steps.js +1 -1
- package/dist/contract/trace/trace-steps.js.map +1 -1
- package/dist/contract/trace/trace-zip-limits.d.ts +5 -1
- package/dist/contract/trace/trace-zip-limits.d.ts.map +1 -1
- package/dist/contract/trace/trace-zip-limits.js +6 -2
- package/dist/contract/trace/trace-zip-limits.js.map +1 -1
- package/dist/decisions/confirmed-decision-commands.d.ts +11 -0
- package/dist/decisions/confirmed-decision-commands.d.ts.map +1 -1
- package/dist/decisions/confirmed-decision-commands.js +21 -3
- package/dist/decisions/confirmed-decision-commands.js.map +1 -1
- package/dist/decisions/human-confirmation.d.ts +2 -0
- package/dist/decisions/human-confirmation.d.ts.map +1 -1
- package/dist/decisions/human-confirmation.js +6 -0
- package/dist/decisions/human-confirmation.js.map +1 -1
- package/dist/exporters/matrix/xlsx-cells.js +1 -1
- package/dist/exporters/matrix/xlsx-cells.js.map +1 -1
- package/dist/graph/evaluate-graph.d.ts +3 -1
- package/dist/graph/evaluate-graph.d.ts.map +1 -1
- package/dist/graph/evaluate-graph.js +8 -5
- package/dist/graph/evaluate-graph.js.map +1 -1
- package/dist/graph/node-decl.d.ts +7 -0
- package/dist/graph/node-decl.d.ts.map +1 -1
- package/dist/graph/node-decl.js.map +1 -1
- package/dist/graph/nodes/test-case-contract-nodes.d.ts +3 -1
- package/dist/graph/nodes/test-case-contract-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/test-case-contract-nodes.js +7 -4
- package/dist/graph/nodes/test-case-contract-nodes.js.map +1 -1
- package/dist/graph/nodes/unit-contract-node.d.ts +2 -1
- package/dist/graph/nodes/unit-contract-node.d.ts.map +1 -1
- package/dist/graph/nodes/unit-contract-node.js +8 -1
- package/dist/graph/nodes/unit-contract-node.js.map +1 -1
- package/dist/graph/nodes/unit-design-nodes.d.ts.map +1 -1
- package/dist/graph/nodes/unit-design-nodes.js +2 -1
- package/dist/graph/nodes/unit-design-nodes.js.map +1 -1
- package/dist/harness/catalog/gate-rules.yaml +2 -2
- package/dist/harness/gates/audit-report-verification.d.ts +15 -6
- package/dist/harness/gates/audit-report-verification.d.ts.map +1 -1
- package/dist/harness/gates/audit-report-verification.js +49 -20
- package/dist/harness/gates/audit-report-verification.js.map +1 -1
- package/dist/harness/gates/delivery-checklist.js +2 -2
- package/dist/harness/gates/delivery-checklist.js.map +1 -1
- package/dist/harness/gates/delivery-decision-gate.d.ts +4 -0
- package/dist/harness/gates/delivery-decision-gate.d.ts.map +1 -1
- package/dist/harness/gates/delivery-decision-gate.js +26 -1
- package/dist/harness/gates/delivery-decision-gate.js.map +1 -1
- package/dist/harness/gates/delivery-decision-step.d.ts +4 -1
- package/dist/harness/gates/delivery-decision-step.d.ts.map +1 -1
- package/dist/harness/gates/delivery-decision-step.js +17 -2
- package/dist/harness/gates/delivery-decision-step.js.map +1 -1
- package/dist/harness/journey.d.ts +19 -0
- package/dist/harness/journey.d.ts.map +1 -1
- package/dist/harness/journey.js +94 -11
- package/dist/harness/journey.js.map +1 -1
- package/dist/harness/provenance.d.ts +29 -0
- package/dist/harness/provenance.d.ts.map +1 -1
- package/dist/harness/provenance.js +109 -9
- package/dist/harness/provenance.js.map +1 -1
- package/dist/hooks/guard-protected-paths.d.ts.map +1 -1
- package/dist/hooks/guard-protected-paths.js +3 -2
- package/dist/hooks/guard-protected-paths.js.map +1 -1
- package/dist/hooks/guard-rules.d.ts +1 -1
- package/dist/hooks/guard-tty-spoof-rule.d.ts +1 -1
- package/dist/hooks/guard-tty-spoof-rule.js +2 -2
- package/dist/hooks/guard-tty-spoof-rule.js.map +1 -1
- package/dist/hooks/hand-back-core.d.ts +5 -3
- package/dist/hooks/hand-back-core.d.ts.map +1 -1
- package/dist/hooks/hand-back-core.js +17 -10
- package/dist/hooks/hand-back-core.js.map +1 -1
- package/dist/hooks/session-start-unit-state-hook.d.ts.map +1 -1
- package/dist/hooks/session-start-unit-state-hook.js +5 -3
- package/dist/hooks/session-start-unit-state-hook.js.map +1 -1
- package/dist/orchestrator/assets-drift.d.ts +5 -0
- package/dist/orchestrator/assets-drift.d.ts.map +1 -1
- package/dist/orchestrator/assets-drift.js +12 -3
- package/dist/orchestrator/assets-drift.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +1 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +2 -2
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +1 -1
- package/package.json +3 -3
- package/src/capabilities/discover.ts +15 -2
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/contract-derive-command.ts +7 -6
- package/src/cli/commands/contract.ts +1 -1
- package/src/cli/commands/delivery.ts +7 -2
- package/src/cli/commands/journey.ts +4 -4
- package/src/cli/commands/next.ts +8 -6
- package/src/cli/index.ts +1 -1
- package/src/cli/project-sungen-invocation.ts +39 -0
- package/src/contract/derive-free-feature.ts +12 -2
- package/src/contract/trace/collect-trace-evidence.ts +1 -1
- package/src/contract/trace/test-step-join.ts +6 -5
- package/src/contract/trace/trace-file-reader.ts +17 -7
- package/src/contract/trace/trace-run-record.ts +14 -8
- package/src/contract/trace/trace-steps.ts +1 -1
- package/src/contract/trace/trace-zip-limits.ts +6 -2
- package/src/decisions/confirmed-decision-commands.ts +25 -4
- package/src/decisions/human-confirmation.ts +5 -0
- package/src/exporters/matrix/xlsx-cells.ts +1 -1
- package/src/graph/evaluate-graph.ts +9 -5
- package/src/graph/node-decl.ts +7 -0
- package/src/graph/nodes/test-case-contract-nodes.ts +9 -4
- package/src/graph/nodes/unit-contract-node.ts +8 -1
- package/src/graph/nodes/unit-design-nodes.ts +2 -1
- package/src/harness/catalog/gate-rules.yaml +2 -2
- package/src/harness/gates/audit-report-verification.ts +51 -23
- package/src/harness/gates/delivery-checklist.ts +2 -2
- package/src/harness/gates/delivery-decision-gate.ts +29 -1
- package/src/harness/gates/delivery-decision-step.ts +19 -3
- package/src/harness/journey.ts +95 -13
- package/src/harness/provenance.ts +99 -11
- package/src/hooks/guard-protected-paths.ts +3 -2
- package/src/hooks/guard-rules.ts +1 -1
- package/src/hooks/guard-tty-spoof-rule.ts +3 -3
- package/src/hooks/hand-back-core.ts +17 -11
- package/src/hooks/session-start-unit-state-hook.ts +5 -3
- package/src/orchestrator/assets-drift.ts +12 -5
- package/src/orchestrator/project-initializer.ts +1 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +2 -2
- package/src/orchestrator/templates/ai-src/commands/run-test.md +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Guard rule `tty-spoof` (phase 06, M5): ASK before an agent runs a human-confirmed sungen command
|
|
3
3
|
* through a terminal emulator.
|
|
4
4
|
*
|
|
5
|
-
* `contract approve`, `journey --waive` and `journey --
|
|
5
|
+
* `contract approve`, `journey --waive`, `journey --decide` and `journey --signoff` refuse without a TTY (DOS-13.4), and
|
|
6
6
|
* an agent's Bash has none — unless it borrows one: `script`, `expect`, `unbuffer`, or a pty from
|
|
7
7
|
* Python/Perl/zsh. Then the prompt sees a "terminal" and a piped phrase reads as a human typing it.
|
|
8
8
|
* This rule spots the pair in the raw command (quotes included, so `bash -c "…"` and `expect -c '…'`
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import type { GuardRule } from './guard-rules';
|
|
14
14
|
|
|
15
15
|
const TTY_EMULATOR = /\b(?:script|unbuffer|expect|pty|zpty|openpty|forkpty|ptyprocess)\b/i;
|
|
16
|
-
const HUMAN_GATED = /\bsungen\b[\s\S]*?\b(?:contract['",\s]+approve\b|journey\b[\s\S]*?--(?:waive|decide)\b)/i;
|
|
16
|
+
const HUMAN_GATED = /\bsungen\b[\s\S]*?\b(?:contract['",\s]+approve\b|journey\b[\s\S]*?--(?:waive|decide|signoff)\b)/i;
|
|
17
17
|
|
|
18
18
|
export const ttySpoof: GuardRule = {
|
|
19
19
|
id: 'tty-spoof',
|
|
@@ -23,7 +23,7 @@ export const ttySpoof: GuardRule = {
|
|
|
23
23
|
if (!cmd || !TTY_EMULATOR.test(cmd) || !HUMAN_GATED.test(cmd)) return null;
|
|
24
24
|
return {
|
|
25
25
|
status: 'ask',
|
|
26
|
-
askReason: 'sungen: this runs a human-confirmed sungen action (`contract approve` / `journey --waive|--decide`) through a terminal '
|
|
26
|
+
askReason: 'sungen: this runs a human-confirmed sungen action (`contract approve` / `journey --waive|--decide|--signoff`) through a terminal '
|
|
27
27
|
+ 'emulator (script / expect / unbuffer / pty). Those confirmations exist so a PERSON at a terminal decides — allow only if you '
|
|
28
28
|
+ 'are that person and asked for exactly this; otherwise run the command yourself in your own terminal.',
|
|
29
29
|
};
|
|
@@ -10,23 +10,27 @@
|
|
|
10
10
|
* commander's preAction, so a usage error — which never reaches an action — and `main().catch`
|
|
11
11
|
* ended in silence. The state now starts the moment parsing does; the action only refines it.
|
|
12
12
|
*/
|
|
13
|
+
import { sungenInvocation, withProjectInvocation } from '../cli/project-sungen-invocation';
|
|
13
14
|
import { resolveUnitCandidates } from '../cli/resolve-unit';
|
|
14
15
|
import { readUnitState, NextStep, UnitState } from '../harness/next-step';
|
|
15
16
|
import { NO_OP, HookContext, HookInput, HookResult } from './hook-result-contract';
|
|
16
17
|
import { findSungenRoot, findUnit, nearestUnit, UnitRef } from './sungen-project-root';
|
|
17
18
|
|
|
18
|
-
/** `
|
|
19
|
-
const SILENT_COMMANDS = new Set(['
|
|
19
|
+
/** `hook` speaks JSON to Claude Code on stdout: it never prints a hand-back. */
|
|
20
|
+
const SILENT_COMMANDS = new Set(['hook']);
|
|
21
|
+
/** `next` IS the answer when it succeeds; a failed `next` answered nothing and hands back like any failure (#937). */
|
|
22
|
+
const ANSWER_COMMANDS = new Set(['next']);
|
|
20
23
|
const UNIT_FLAG = /^(?:--screen|-s|--flow|--area|--api)$/;
|
|
21
24
|
const UNIT_FLAG_EQ = /^--(?:screen|flow|area|api)=(.+)$/;
|
|
22
25
|
/** A unit id is a name or `flows/<name>` — anything else is not offered back to the operator. */
|
|
23
26
|
const SAFE_UNIT = /^[\w.-]+(?:\/[\w.-]+)?$/;
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
/** `invocation`: how the project runs sungen (`npx sungen` with a local install, #939). */
|
|
29
|
+
export function handBackLine(unit?: string, invocation = 'sungen'): string {
|
|
30
|
+
return `Next: ${invocation} next${unit ? ` ${unit}` : ''}`;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
|
-
export interface CliHandBackState { unit?: string; machine: boolean; silent: boolean; actionRan: boolean }
|
|
33
|
+
export interface CliHandBackState { unit?: string; machine: boolean; silent: boolean; answer: boolean; actionRan: boolean }
|
|
30
34
|
|
|
31
35
|
/** The default, set before commander parses — what a usage error or a fatal error hands back. */
|
|
32
36
|
export function initialHandBackState(argv: string[]): CliHandBackState {
|
|
@@ -38,13 +42,14 @@ export function initialHandBackState(argv: string[]): CliHandBackState {
|
|
|
38
42
|
else if (UNIT_FLAG.test(args[i]) && args[i + 1] && !args[i + 1].startsWith('-')) unit ??= args[++i];
|
|
39
43
|
}
|
|
40
44
|
const command = args.find((a) => !a.startsWith('-')) ?? '';
|
|
41
|
-
return { unit, machine: args.includes('--json') || args.includes('--grep'), silent: SILENT_COMMANDS.has(command), actionRan: false };
|
|
45
|
+
return { unit, machine: args.includes('--json') || args.includes('--grep'), silent: SILENT_COMMANDS.has(command), answer: ANSWER_COMMANDS.has(command), actionRan: false };
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
/** commander's preAction: the action now KNOWS its unit and output mode. */
|
|
45
49
|
export function onCliAction(state: CliHandBackState, name: string, opts: Record<string, unknown>, args: unknown[]): void {
|
|
46
50
|
state.actionRan = true;
|
|
47
51
|
if (SILENT_COMMANDS.has(name)) { state.silent = true; return; }
|
|
52
|
+
state.answer = ANSWER_COMMANDS.has(name);
|
|
48
53
|
const flagged = [opts.screen, opts.flow, opts.area, opts.api].find((v) => typeof v === 'string' && v) as string | undefined;
|
|
49
54
|
state.unit = flagged ?? (typeof args?.[0] === 'string' ? args[0] : undefined);
|
|
50
55
|
// `--grep` is machine output too: callers capture it with `$(…)` (#836).
|
|
@@ -57,10 +62,11 @@ export function onCliAction(state: CliHandBackState, name: string, opts: Record<
|
|
|
57
62
|
*/
|
|
58
63
|
export function exitHandBack(state: CliHandBackState, code: number, cwd: string): { stream: 'stdout' | 'stderr'; text: string } | null {
|
|
59
64
|
if (state.silent) return null;
|
|
65
|
+
if (state.answer && code === 0) return null; // a successful `next` is the hand-back itself
|
|
60
66
|
if (!state.actionRan && code === 0) return null; // --help / --version printed what was asked
|
|
61
67
|
const unit = state.unit && SAFE_UNIT.test(state.unit) && !state.unit.split('/').includes('..')
|
|
62
68
|
&& resolveUnitCandidates(state.unit, cwd).length > 0 ? state.unit : undefined;
|
|
63
|
-
const line = handBackLine(unit);
|
|
69
|
+
const line = handBackLine(unit, sungenInvocation(findSungenRoot(cwd) ?? cwd));
|
|
64
70
|
if (code === 1) return { stream: 'stderr', text: `${line}\n` }; // after the error, one line
|
|
65
71
|
return { stream: state.machine ? 'stderr' : 'stdout', text: `\n${line}\n` };
|
|
66
72
|
}
|
|
@@ -81,10 +87,10 @@ export function attachCliHandBack(argv: string[], cwd: string): { onAction: (nam
|
|
|
81
87
|
|
|
82
88
|
const MARK: Record<NextStep['kind'], string> = { do: '→', blocked: '⛔', optional: '○', decide: '⚖' };
|
|
83
89
|
|
|
84
|
-
/** `sungen next` rendered compactly for an agent's context. */
|
|
85
|
-
export function formatSteps(steps: NextStep[], max = 4): string[] {
|
|
90
|
+
/** `sungen next` rendered compactly for an agent's context; commands as `root` runs sungen. */
|
|
91
|
+
export function formatSteps(steps: NextStep[], max = 4, root?: string): string[] {
|
|
86
92
|
return steps.slice(0, max).map((st) =>
|
|
87
|
-
` ${MARK[st.kind]} ${st.command}${st.kind === 'decide' ? ' (a QA decision — ask, do not run)' : ''}\n ${st.because}`);
|
|
93
|
+
` ${MARK[st.kind]} ${root ? withProjectInvocation(st.command, root) : st.command}${st.kind === 'decide' ? ' (a QA decision — ask, do not run)' : ''}\n ${st.because}`);
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
/** The unit a session is on (a named one when it exists, else the nearest) and its derived state. */
|
|
@@ -101,6 +107,6 @@ export function run(input: HookInput, ctx: HookContext): HookResult {
|
|
|
101
107
|
if (!found || found.state.steps.length === 0) return NO_OP;
|
|
102
108
|
return {
|
|
103
109
|
status: 'context',
|
|
104
|
-
output: [`${handBackLine(found.unit.name)} — derived from what the project holds now:`, ...formatSteps(found.state.steps)].join('\n'),
|
|
110
|
+
output: [`${handBackLine(found.unit.name, sungenInvocation(found.root))} — derived from what the project holds now:`, ...formatSteps(found.state.steps, 4, found.root)].join('\n'),
|
|
105
111
|
};
|
|
106
112
|
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { NO_OP, HookMeta, SungenHook } from './hook-result-contract';
|
|
11
11
|
import { formatSteps, locateUnitState } from './hand-back-core';
|
|
12
|
+
import { sungenInvocation } from '../cli/project-sungen-invocation';
|
|
12
13
|
import { listUnits } from './sungen-project-root';
|
|
13
14
|
import type { UnitState } from '../harness/next-step';
|
|
14
15
|
|
|
@@ -40,14 +41,15 @@ export const sessionStartUnitStateHook: SungenHook = {
|
|
|
40
41
|
if (!found) return NO_OP;
|
|
41
42
|
const { root, unit, state } = found;
|
|
42
43
|
const others = listUnits(root).length - 1;
|
|
44
|
+
const sungen = sungenInvocation(root);
|
|
43
45
|
const lines = [
|
|
44
46
|
`[sungen] Unit in progress: ${unit.name} (${unit.kind})${others > 0 ? ` — the project holds ${others} other unit(s)` : ''}.`,
|
|
45
47
|
`Already done — do not redo: ${held(state)}.`,
|
|
46
48
|
state.audit ? `Audit: ${state.audit.overall}/10 [${state.audit.gateStatus}] · ${state.audit.findings.length} finding(s).` : '',
|
|
47
|
-
`Next (
|
|
48
|
-
...formatSteps(state.steps),
|
|
49
|
+
`Next (\`${sungen} next ${unit.name}\`, derived from the project state):`,
|
|
50
|
+
...formatSteps(state.steps, 4, root),
|
|
49
51
|
input.source === 'compact' ? 'Context was compacted: re-read the unit\'s files before acting on them.' : '',
|
|
50
|
-
others > 0 ?
|
|
52
|
+
others > 0 ? `Working on another unit? Ask \`${sungen} next <unit>\` (or \`${sungen} next\` for all).` : '',
|
|
51
53
|
];
|
|
52
54
|
return { status: 'context', output: lines.filter(Boolean).join('\n') };
|
|
53
55
|
},
|
|
@@ -2,6 +2,7 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { getPackageVersion } from '../exporters/package-info';
|
|
4
4
|
import { readJsonConfig, warnUnreadableConfig } from '../harness/config-file';
|
|
5
|
+
import { updateCommand } from '../cli/project-sungen-invocation';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Are this project's AI assets (commands + skills under `.claude/` etc.) the ones
|
|
@@ -54,6 +55,16 @@ export function checkAssetsDrift(cwd: string): AssetsDrift | null {
|
|
|
54
55
|
return { recordedVersion, runningVersion, changed, total };
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* The note's text. The update command is the one this project runs (`npx sungen` with a local install)
|
|
60
|
+
* on the channel it runs (`--alpha` / `--beta`): a plain `sungen update` would switch to stable (#939).
|
|
61
|
+
*/
|
|
62
|
+
export function assetsDriftNote(drift: AssetsDrift, cwd: string, paint: (cmd: string) => string = (c) => c): string {
|
|
63
|
+
return `note: this project's AI commands/skills were written by sungen ${drift.recordedVersion}, ` +
|
|
64
|
+
`you are running ${drift.runningVersion} — run ${paint(updateCommand(cwd, drift.runningVersion))} to refresh them ` +
|
|
65
|
+
'(upgrading the package alone does not).';
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
/** One line, on stderr so `--json` output stays machine-readable. */
|
|
58
69
|
export function warnAssetsDrift(cwd: string): void {
|
|
59
70
|
const drift = checkAssetsDrift(cwd);
|
|
@@ -61,9 +72,5 @@ export function warnAssetsDrift(cwd: string): void {
|
|
|
61
72
|
const gray = '\x1b[90m';
|
|
62
73
|
const cyan = '\x1b[36m';
|
|
63
74
|
const reset = '\x1b[0m';
|
|
64
|
-
process.stderr.write(
|
|
65
|
-
`${gray}note: this project's AI commands/skills were written by sungen ${drift.recordedVersion}, ` +
|
|
66
|
-
`you are running ${drift.runningVersion} — run ${cyan}sungen update${reset}${gray} to refresh them ` +
|
|
67
|
-
`(upgrading the package alone does not).${reset}\n`,
|
|
68
|
-
);
|
|
75
|
+
process.stderr.write(`${gray}${assetsDriftNote(drift, cwd, (c) => `${cyan}${c}${reset}${gray}`)}${reset}\n`);
|
|
69
76
|
}
|
|
@@ -294,6 +294,7 @@ export class ProjectInitializer {
|
|
|
294
294
|
'specs/.auth-mobile/', // mobile auth contract = credentials (sensitive)
|
|
295
295
|
'.env.appium', // local mobile-test env = machine paths + device/team ids (.example IS committed)
|
|
296
296
|
'test-results/',
|
|
297
|
+
'test-results-free/', // free / @impl:code runs: their own outputDir, so a compiled run cannot clear their traces (#941)
|
|
297
298
|
'playwright-report/',
|
|
298
299
|
'.playwright-mcp/',
|
|
299
300
|
'.sungen/figma-cache/', // raw Figma node JSON (design IP)
|
|
@@ -102,8 +102,8 @@ declaration instead of leaving both on record.
|
|
|
102
102
|
|
|
103
103
|
## ⛔ HARD RULE — approvals and waivers are a human's, on a terminal
|
|
104
104
|
|
|
105
|
-
`sungen contract approve`, `sungen journey --waive` and `sungen journey --
|
|
106
|
-
decision and ask for a typed confirmation on a real terminal (TTY). Never run them, never fake the
|
|
105
|
+
`sungen contract approve`, `sungen journey --waive`, `sungen journey --decide` and `sungen journey --signoff` record a QA
|
|
106
|
+
decision (the sign-off is the single human gate before delivery) and ask for a typed confirmation on a real terminal (TTY). Never run them, never fake the
|
|
107
107
|
terminal (`script`, `expect`, a pty wrapper), and never edit `contracts.lock.yaml` or
|
|
108
108
|
`qa/**/decisions/` by hand. When one is due, show the exact command and stop for the human.
|
|
109
109
|
|
|
@@ -268,7 +268,7 @@ question already.
|
|
|
268
268
|
7. **Phase 3 — Full Run**: Run all tests. Fix only **new** failures (elements unique to `@normal`/`@low`). Max 1 attempt. Don't loop on low-priority failures.
|
|
269
269
|
8. **Phase 4 — Regression**: One final full run. No more fix loops. Then run `sungen triage --screen <name>` and render its table as the results summary: every red or flaky scenario already sits in one class (`product-candidate`, `unclassified`, `test-defect`, `environment`, `blocked-capability`, `known-defect`, `flaky`) with the rule and one evidence line. You may move an `unclassified` row into a class only with a written evidence line of your own. You may never turn a `product-candidate` into a `test-defect` by editing the `.feature` or the expected value — the Gherkin is frozen; report it as a candidate bug. For each `product-candidate` you judge to be a product bug, run `sungen defect draft --screen <name> --scenario "<title>"`, fill the record's Summary, check its Expected against `spec.md`, then add `@known-defect:DEF-NNN` to that scenario's tag line (the tag-only exception above). The severity in the draft is `proposed` — confirm it with QA (`AskUserQuestion`) before the hand-back and set `severity_source: qa`. A row you judge is not a product bug stays in the summary with your reason, and gets no record. A `bare-timeout` row (`unclassified`) is never re-run inside this command — Phase 4 was the final run; offer the re-run in Next steps.
|
|
270
270
|
8a. **Test Contract — every unit with a lock.** When `qa/<screens|flows|api>/<name>/contracts.lock.yaml` exists, check the suite still does what QA approved:
|
|
271
|
-
- **`@free` / `@impl:code` scenarios** (hand-written specs, not compiled):
|
|
271
|
+
- **`@free` / `@impl:code` scenarios** (hand-written specs, not compiled): after the Phase 4 final run, run their specs with the project's free-test config and `--trace on` (`npx playwright test -c <free-test config> --trace on`) — their trace is the only proof. That config writes to its own `outputDir: 'test-results-free'` with `trace: 'on'` and the sungen run-record reporter; never run them into `test-results/`, which the next compiled run clears (the trace is then gone and the scenario is `unproven`). If the project has no such config, no trace is admissible: tell the QA, do not write or edit the config yourself. Then `sungen contract check --screen <name> --trace`.
|
|
272
272
|
- **No free tests**: `sungen contract check --screen <name>`.
|
|
273
273
|
- **Exit 2 (hard)** — `oracle-weakened`, `target-changed`, `assertion-lost`, `lock-uncommitted`, `unproven` → report each finding to the QA in the results summary, with its rule. **Never** edit the `.feature`, an expected value, the lock or a decision to turn it green, and **never** run `sungen contract approve` — approving is a QA's decision, on a terminal. **Exit 0 with `review`** → list it for the QA: review blocks `sungen delivery` until QA re-approves. **Exit 1** → report the error. No lock → skip: every scenario is `proposed`, which never blocks.
|
|
274
274
|
9. **Phase 8 — Serial-cascade risk check** (#414): If any scenario has many steps, a skipped test may cause downstream steps to be incorrectly skipped due to the serial-cascade sensor detecting false "not reached" conditions. When you see "test skipped" messages during Phase 3–4 runs, check if the skipped scenario is a **blockers-exist** dependency in the test ledger (`sungen ledger query --screen <name>`). If the scenario had high step count and cascade fired, offer: *"Scenarios with deep steps can trigger serial-cascade skipping. Consider parallelizing this spec with `@concurrent` tags or breaking into smaller scenarios."* Let the user decide whether to refactor (do not auto-apply parallelization).
|