axstack 0.53.1 → 0.55.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.
@@ -0,0 +1,48 @@
1
+ import { parseProc, pressure } from './fleet-proc.js';
2
+ import { validateManifest, sampleOptions } from './fleet-sample-manifest.js';
3
+ import { readProc, wait } from './fleet-sample-io.js';
4
+ export { parseProc, pressure, validateManifest };
5
+
6
+ // Read functions and clocks are injectable for deterministic offline verification.
7
+ export async function sampleFleet(manifest, hosts, options, dependencies = {}) {
8
+ validateManifest(manifest, hosts);
9
+ const { intervalMs, timeoutMs } = sampleOptions(options);
10
+ const { clock = Date.now, monotonic = () => performance.now(), read = readProc, wait: pause = wait,
11
+ signal } = dependencies;
12
+ const telemetry = await Promise.all(manifest.hosts.map(async (host) => {
13
+ const controller = new AbortController();
14
+ const cancel = () => controller.abort(Error('cancelled'));
15
+ signal?.addEventListener('abort', cancel, { once: true });
16
+ if (signal?.aborted) cancel();
17
+ const timer = setTimeout(() => controller.abort(Error('timeout')), timeoutMs);
18
+ const windowStartedAt = clock(); const started = monotonic();
19
+ const source = host.transport === 'local' ? 'linux-proc-local' : 'linux-proc-ssh';
20
+ const provenance = { hostId: host.hostId, environmentId: host.environmentId, scope: 'whole-host', source,
21
+ intervalMs, timeoutMs, windowStartedAt, windowKind: 'collector-bracket' };
22
+ const check = () => { if (controller.signal.aborted) throw controller.signal.reason; };
23
+ try {
24
+ check();
25
+ const first = parseProc(await read(host, controller.signal));
26
+ check();
27
+ await pause(intervalMs, controller.signal);
28
+ check();
29
+ const last = parseProc(await read(host, controller.signal));
30
+ check();
31
+ const windowEndedAt = clock(); const durationMs = Math.floor(monotonic() - started);
32
+ if (durationMs >= timeoutMs) throw Error('timeout');
33
+ if (durationMs < intervalMs || windowEndedAt < windowStartedAt
34
+ || windowEndedAt - windowStartedAt < durationMs
35
+ || windowEndedAt - windowStartedAt > durationMs + 1000) throw Error('collector-clock');
36
+ return { ...provenance, status: 'ok', ...pressure(first, last), durationMs,
37
+ windowEndedAt, receivedAt: windowEndedAt };
38
+ } catch (error) {
39
+ const reason = controller.signal.aborted ? controller.signal.reason?.message : error.message;
40
+ return { ...provenance, status: ['timeout', 'cancelled', 'malformed', 'inconsistent-counters', 'collector-clock']
41
+ .includes(reason) ? reason : 'failed', durationMs: Math.max(0, Math.floor(monotonic() - started)), receivedAt: clock() };
42
+ } finally {
43
+ clearTimeout(timer);
44
+ signal?.removeEventListener('abort', cancel);
45
+ }
46
+ }));
47
+ return { schemaVersion: 1, telemetry };
48
+ }
@@ -3,6 +3,7 @@ import { readFileSync } from 'node:fs';
3
3
  const bindings = { codex: 'codex', claude: 'claudeAgent', grok: 'grok' };
4
4
  const effortIds = { codex: 'reasoningEffort', claude: 'effort', grok: 'reasoningEffort' };
5
5
  const nonempty = (value) => typeof value === 'string' && value.trim().length > 0;
6
+ const optionValues = (option) => option.values ?? option.options?.map((value) => value.id);
6
7
 
7
8
  function argumentsFrom(args) {
8
9
  const options = { excluded: [] };
@@ -37,9 +38,11 @@ function providerFrom(catalog, provider) {
37
38
  }
38
39
  const instance = matches[0];
39
40
  if (!Array.isArray(instance.models) || instance.models.some((model) =>
40
- !model || !nonempty(model.id) || !Array.isArray(model.options) || model.options.some((option) =>
41
+ !model || !nonempty(model.id) || !Array.isArray(model.options ?? []) || (model.options ?? []).some((option) =>
41
42
  !option || !nonempty(option.id) || (option.options !== undefined &&
42
- (!Array.isArray(option.options) || option.options.some((value) => !value || !nonempty(value.id))))))) {
43
+ (!Array.isArray(option.options) || option.options.some((value) => !value || !nonempty(value.id))))
44
+ || (option.values !== undefined && (!Array.isArray(option.values)
45
+ || option.values.some((value) => typeof value !== 'boolean' && !nonempty(value))))))) {
43
46
  throw new Error('malformed capabilities models or options');
44
47
  }
45
48
  return instance;
@@ -58,6 +61,7 @@ function modelFrom(models, { provider, model: pin, class: modelClass, excluded }
58
61
  }
59
62
  const model = models[0];
60
63
  if (!model || excluded.includes(model.id)) throw new Error('missing first listed model');
64
+ if (!model.id.startsWith('grok-')) throw new Error(`first listed model ${model.id} is not grok-family`);
61
65
  return model;
62
66
  }
63
67
  if (!['codex', 'claude'].includes(provider) || !/^[a-z]+$/.test(modelClass)) {
@@ -90,8 +94,8 @@ try {
90
94
  if ((options.class === 'terra' || /^gpt-\d+(?:\.\d+)*-terra$/.test(model.id)) && effort !== 'high') {
91
95
  throw new Error('Terra requires exactly high effort');
92
96
  }
93
- const effortOptions = model.options.filter((option) => option.id === effortIds[provider]);
94
- if (effortOptions.length !== 1 || !effortOptions[0].options?.some((value) => value.id === effort)
97
+ const effortOptions = (model.options ?? []).filter((option) => option.id === effortIds[provider]);
98
+ if (effortOptions.length !== 1 || !optionValues(effortOptions[0])?.includes(effort)
95
99
  || (provider === 'grok' && effort === 'max')) {
96
100
  throw new Error(`unsupported effort ${effort} for ${provider}/${model.id}`);
97
101
  }
@@ -137,7 +137,7 @@ This section applies to peer and authored PR modes.
137
137
  For an `axstack-verify create` PR, the reviewer drives the candidate's recipe because the base has none.
138
138
  The author never performs this review drive.
139
139
  The reviewer drives each affected feature and records that drive as the `Final-head check`.
140
- Delegate browser drives to `axstack-ui-verifier` with the feature file path.
140
+ Delegate browser drives to `axstack-ui-verifier` with the feature file path; device drives follow [UI verification](../../axstack/references/ui-verification.md#android-device-and-webpwa-dual-run)'s dual-run trigger.
141
141
  Running the applicable surface counts as spin-up; a CLI run qualifies.
142
142
  Use loopback and isolated instances under [axstack-verify](../../axstack-verify/SKILL.md#isolate-every-attempt).
143
143
 
@@ -43,8 +43,9 @@ and the lifecycle's [audit skill](../axstack-audit/SKILL.md) hook.
43
43
  access before any issue write. Record the repository and identity checked.
44
44
  Missing access preserves the GitHub selection and stops the phase without
45
45
  mutation or fallback. Markdown mode skips external access preflight.
46
- 3. **Draft with decision evidence.** Write observable acceptance criteria
47
- and explicit exclusions in the selected store. Only when the scope identity
46
+ 3. **Draft with decision evidence.** Follow the
47
+ [readable specification format](references/format.md) when drafting in the
48
+ selected store. Only when the scope identity
48
49
  carries a sketch, include the [design lens](../axstack/references/design-lens.md)
49
50
  sketch in the approved revision's `Design` section and its `Usage` line in
50
51
  acceptance. First record the driver's
@@ -90,7 +91,8 @@ and the lifecycle's [audit skill](../axstack-audit/SKILL.md) hook.
90
91
  [Diligence](../axstack/references/diligence.md) to check the draft against
91
92
  the Align decisions for anything dropped, added, or softened.
92
93
  5. **Snapshot the baseline.** Record the approved revision identity and a
93
- concise repository Markdown counterpart. In Linear mode, the native
94
+ concise repository Markdown counterpart pointing to the same requirements,
95
+ not a second human or agent contract. In Linear mode, the native
94
96
  document remains authoritative. In GitHub mode, the approved issue body is
95
97
  authoritative and its GitHub issue URL plus SHA-256 body digest identifies
96
98
  the exact approved revision. In Markdown mode, the agreed repository path does.
@@ -0,0 +1,88 @@
1
+ # Readable specification format
2
+
3
+ Use this structure inside the selected authoritative store. It changes layout,
4
+ not store selection, adviser review, diligence, approval or snapshot authority.
5
+
6
+ ## Decision brief
7
+
8
+ Begin with a decision brief before execution detail and history.
9
+ Keep identity and status compact, with review-round history after current requirements.
10
+ For example: title, revision, draft or approved status, authoritative location.
11
+
12
+ - In the opening, explain the problem, why it matters and the proposed change.
13
+ - When useful, include a concrete before/after example or flow in the opening.
14
+ - State included and excluded scope in the brief.
15
+ - Surface consequential risks, trade-offs and unverified boundaries in the brief.
16
+ - Collect all decisions needed from the user in the opening, with options and
17
+ consequences; say explicitly when none remain.
18
+
19
+ Give enough context for an informed decision. Link each consequential choice
20
+ or risk to its precise execution requirement. Never bury a material decision
21
+ or risk only in supporting detail.
22
+
23
+ ## Execution requirements
24
+
25
+ Group precise requirements and observable acceptance by capability, using stable
26
+ IDs where useful. Include behavior, interfaces, data, invariants, failure cases
27
+ and validation that determine success. Put routine file-by-file task and PR
28
+ sequencing in Tickets, retaining meaningful design and rollout constraints in the spec.
29
+ State each rule once and reference it from other sections.
30
+ Technical execution detail remains binding wherever stored, including linked detail.
31
+ Human and agent views share one authoritative specification.
32
+ Resolve brief/detail conflicts before approval rather than declaring one view expendable.
33
+
34
+ Use headings, short requirement blocks or lists for scanning. Use a table when
35
+ it helps compare choices, rather than putting whole paragraphs in cells.
36
+ Do not impose word caps, mandatory tables, an extra artifact or ceremony.
37
+ The example below is adaptable guidance, not another required document.
38
+ The existing readback identifies and explains this same revision; it is not
39
+ a duplicate whole spec with separate requirements.
40
+
41
+ ## Evidence and history
42
+
43
+ Keep history, evidence and adviser receipts separate from current requirements.
44
+ Place provenance, commands, review-round dispositions and change logs here or
45
+ link their existing records. They explain the requirements, not override them.
46
+ When amending a draft, update current criteria in place and keep the change log
47
+ as history. Preserve approved snapshots immutable and mark supersession through
48
+ metadata or links outside the approved bytes. Use the existing material-revision
49
+ and snapshot procedure for a changed baseline; a new draft is not approval.
50
+
51
+ ## Readability review
52
+
53
+ For readability review, verify a human can identify what changes, why, decisions
54
+ and risks from the opening and an implementer can find exact requirements without
55
+ reconstructing history. Assess this alongside completeness before the existing
56
+ specification checkpoint. Return unclear or conflicting passages for revision
57
+ under the existing review and approval rules.
58
+
59
+ ## Compact example
60
+
61
+ ```markdown
62
+ # Export the filtered view — r2, draft
63
+ Authoritative location: <selected store URL or repository path>
64
+
65
+ ## Decision brief
66
+ Problem/why: exports ignore the table filter, forcing users to clean the file.
67
+ Change: export what the user is viewing.
68
+ Before: export downloads the whole dataset. After: it downloads filtered rows.
69
+ Included: CSV export from this table. Excluded: scheduled exports and new formats.
70
+ Trade-off/risk: an active filter can omit rows users expect; label the action
71
+ “Export filtered rows” (EXP-1). Unverified: large exports on low-memory devices.
72
+ Decision needed: approve filtered export with the explicit label, or retain
73
+ whole-dataset export and its cleanup cost. Proposed choice: filtered export.
74
+
75
+ ## Execution requirements
76
+ ### Filtered export
77
+ EXP-1: On click, capture the active filter and visible column order, label the
78
+ action “Export filtered rows”, and export only matching rows in those columns.
79
+ Acceptance for EXP-1: with a two-row dataset and a filter matching one row,
80
+ the CSV contains that row and the visible columns in order; with a filter
81
+ matching zero rows, the CSV contains the header only.
82
+
83
+ ## Evidence and history
84
+ Source: <observed current behavior and revision>.
85
+ Adviser receipts: <links bound to r2>; approval: pending.
86
+ Change log: r2 incorporates the zero-row case into EXP-1 acceptance.
87
+ Previous approved baseline, if any: <immutable ref>; supersession: pending approval.
88
+ ```
@@ -49,6 +49,13 @@ Name the app, surface and invocation conditions in the description.
49
49
  - Cleanup: stop only processes started by this run and remove owned scratch.
50
50
  - Helpers: explain each helper's purpose and working directory.
51
51
 
52
+ For a device surface, require a Device section in generated `verify-<app>` recipes.
53
+ The Device section must use device tools to install and launch the pinned build,
54
+ drive features, capture evidence and shutdown owned devices.
55
+ The Device section must follow [UI verification](../axstack/references/ui-verification.md#android-device-and-webpwa-dual-run)
56
+ for dual-run independence and reconciliation, fleet routing and desktop safety.
57
+ Record unavailable device surfaces as unverified.
58
+
52
59
  Write `.agents/skills/verify-<app>/features/README.md` as an index of the top 3-5 user features.
53
60
  Write per-feature files in `.agents/skills/verify-<app>/features/` and link them from the index.
54
61
  Give each feature file its entry points, prerequisites, drive steps and observable proof.
@@ -79,10 +86,12 @@ Report an unavailable candidate run as unverified.
79
86
 
80
87
  Execute launch, doctor, drive one mapped feature, capture evidence, clean up and confirm evidence survives.
81
88
  Run doctor before each drive.
82
- The author drives only non-browser surfaces.
83
- Delegate browser drives through
84
- [UI verification](../axstack/references/ui-verification.md) to `axstack-ui-verifier`
89
+ The author drives only non-browser, non-device surfaces.
90
+ Delegate a recipe's device checks and explicitly requested app/PWA verification
91
+ to the UI verifier roles for a dual run through
92
+ [UI verification](../axstack/references/ui-verification.md#android-device-and-webpwa-dual-run)
85
93
  with the feature file path in the brief.
94
+ Delegate other recipe browser drives to one `axstack-ui-verifier` pass.
86
95
  Give the verifier the pinned candidate, isolated instance and private evidence path.
87
96
  Clean up after every failed attempt.
88
97
  Retry once after a drift fix.
@@ -124,10 +133,12 @@ Classify each gap as doc drift, harness gap or product regression:
124
133
  Drive every mapped feature live even when source looks clean.
125
134
  Use the skill's Launch recipe for an isolated instance.
126
135
  Run doctor before each drive.
127
- The author drives only non-browser surfaces.
128
- Delegate browser drives through
129
- [UI verification](../axstack/references/ui-verification.md) to `axstack-ui-verifier`
136
+ The author drives only non-browser, non-device surfaces.
137
+ Delegate a recipe's device checks and explicitly requested app/PWA verification
138
+ to the UI verifier roles for a dual run through
139
+ [UI verification](../axstack/references/ui-verification.md#android-device-and-webpwa-dual-run)
130
140
  with the feature file path in the brief.
141
+ Delegate other recipe browser drives to one `axstack-ui-verifier` pass.
131
142
  Capture actions, resulting states and side effects under its proof standards.
132
143
  Report unreachable prerequisites with attempted routes separately from passes.
133
144
  Fix a missing prerequisite in the map as doc drift.
package/src/roles.js CHANGED
@@ -92,8 +92,8 @@ export function assessRoleReadiness(roles, preset) {
92
92
  const isIntentionalAbsence = (role) => role.model === null && (
93
93
  (preset === 'mixed' && role.id === 'axstack-research-x' && role.provider === 'grok') ||
94
94
  (preset === 'mixed' && role.id === 'axstack-arena-candidate-grok' && role.provider === 'grok') ||
95
- (preset === 'codex-only' && ['axstack-advisor-opus', 'axstack-escalation-fable', 'axstack-arena-judge-opus', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-research-web-sol'].includes(role.id)) ||
96
- (preset === 'claude-only' && ['axstack-advisor-astra', 'axstack-arena-judge-astra', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-auditor-sol', 'axstack-research-code-sol', 'axstack-explore-execution-sol', 'axstack-explore-codebase-luna', 'axstack-research-web-sol'].includes(role.id))
95
+ (preset === 'codex-only' && ['axstack-advisor-opus', 'axstack-escalation-fable', 'axstack-arena-judge-opus', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-research-web-sol', 'axstack-ui-verifier-sonnet'].includes(role.id)) ||
96
+ (preset === 'claude-only' && ['axstack-advisor-astra', 'axstack-arena-judge-astra', 'axstack-research-x', 'axstack-arena-candidate-grok', 'axstack-auditor-sol', 'axstack-research-code-sol', 'axstack-explore-execution-sol', 'axstack-explore-codebase-luna', 'axstack-research-web-sol', 'axstack-ui-verifier-sonnet'].includes(role.id))
97
97
  );
98
98
  for (const role of roles) {
99
99
  if (!bounds.has(role.provider)) {