all-hands-cli 0.1.9 → 0.1.11
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.
|
@@ -17,6 +17,16 @@ This is how validation compounds. Every domain has both a stochastic dimension (
|
|
|
17
17
|
|
|
18
18
|
A validation suite must have a meaningful stochastic dimension to justify existing. Deterministic-only tools (type checking, linting, formatting) are test commands referenced directly in acceptance criteria and CI/CD — they are NOT suites.
|
|
19
19
|
|
|
20
|
+
## Repository Agnosticism
|
|
21
|
+
|
|
22
|
+
This reference file is a generic rule file that ships with the harness. It MUST NOT contain references to project-specific validation suites, commands, or infrastructure. All examples must either:
|
|
23
|
+
- Reference existing default validation suites shipped with this repo (currently: xcode-automation, browser-automation)
|
|
24
|
+
- Use generic/hypothetical descriptions that any target repository can map to their own context
|
|
25
|
+
|
|
26
|
+
When examples are needed, use **snippets from the existing default suites** rather than naming suites or commands that belong to a specific target project. Target repositories create their own suites for their domains — this file teaches how to create and structure them, not what they should be called.
|
|
27
|
+
|
|
28
|
+
**Why**: Target repositories consume this file as authoritative guidance. Project-specific references create confusion (agents look for suites that don't exist), couple the harness to a single project, and violate the principle that this file teaches patterns, not inventories. If a pattern needs a concrete example, draw it from xcode-automation or browser-automation.
|
|
29
|
+
|
|
20
30
|
## Creating Validation Tooling
|
|
21
31
|
|
|
22
32
|
Follow `.allhands/flows/shared/CREATE_VALIDATION_TOOLING_SPEC.md` for the full process. This creates a spec, not an implementation.
|
|
@@ -80,6 +90,113 @@ Prompt files reference validation suites in their `validation_suites` frontmatte
|
|
|
80
90
|
2. Agent runs suite's **Deterministic Integration** section for acceptance criteria gating
|
|
81
91
|
3. Validation review (`PROMPT_VALIDATION_REVIEW.md`) confirms pass/fail
|
|
82
92
|
|
|
93
|
+
## Command Documentation Principle
|
|
94
|
+
|
|
95
|
+
Two categories of commands exist in validation suites, each requiring different documentation approaches:
|
|
96
|
+
|
|
97
|
+
**External tooling commands — Document explicitly**: Commands from external tools (`xctrace`, `xcrun simctl`, `agent-browser`, `playwright`, `curl`, etc.) are stable, unfamiliar to agents by default, and unlikely to change with codebase evolution. Document specific commands, flags, and use cases inline with motivations. Example from xcode-automation: `xcrun xctrace record --template 'Time Profiler' --device '<UDID>' --attach '<PID>'` — the flags, ordering constraints, and PID discovery method are all external tool knowledge that the suite documents explicitly.
|
|
98
|
+
|
|
99
|
+
**Internal codebase commands — Document patterns, not inventories**: Project-specific scripts, test commands, and codebase-specific CLI wrappers evolve rapidly. Instead:
|
|
100
|
+
1. **Document core infrastructure commands explicitly** — commands that boot services, manage environments, and are foundational to validation in the target project. These are stable and essential per-project, but suites should teach agents how to discover them (e.g., "check `package.json` scripts" or "run `--help`"), not hardcode specific script names.
|
|
101
|
+
2. **Teach patterns for everything else** — naming conventions, where to discover project commands, what categories mean, and how to build upon them.
|
|
102
|
+
3. **Document motivations** — why different test categories exist, when to use which, what confidence each provides.
|
|
103
|
+
|
|
104
|
+
Per **Frontier Models are Capable**: An agent given patterns + motivations + discovery instructions outperforms one given stale command inventories. Suites that teach patterns age gracefully; suites that enumerate commands require maintenance on every change.
|
|
105
|
+
|
|
106
|
+
## Decision Tree Requirement
|
|
107
|
+
|
|
108
|
+
Every validation suite MUST include a decision tree that routes agents to the correct validation approach based on their situation. Decision trees:
|
|
109
|
+
- Distinguish which instructions are relevant to which validation scenario (e.g., UI-only test vs full E2E with native code changes)
|
|
110
|
+
- Show where/when stochastic vs deterministic testing applies
|
|
111
|
+
- Surface deterministic branch points where other validation suites must be utilized (e.g., "Does this branch have native code changes? → Yes → follow xcode-automation decision tree")
|
|
112
|
+
- Cleanly articulate multiple expected use cases within a single suite
|
|
113
|
+
|
|
114
|
+
The decision tree replaces flat prerequisite lists with structured routing. An agent reads the tree and follows the branch matching their situation, skipping irrelevant setup and finding the right cross-references.
|
|
115
|
+
|
|
116
|
+
## tmux Session Management Standard
|
|
117
|
+
|
|
118
|
+
All suites that require long-running processes (dev servers, Expo servers, Flask API, Metro bundler) MUST use the tmux approach proven in xcode-automation:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# CRITICAL: -t $TMUX_PANE pins split to agent's window, not user's focused window
|
|
122
|
+
tmux split-window -h -d -t $TMUX_PANE \
|
|
123
|
+
-c /path/to/repo '<command>'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Observability**: Agents MUST verify processes are running correctly via tmux pane capture (`tmux capture-pane -p -t <pane_id>`) before proceeding with validation. This prevents silent failures where a dev server fails to start but the agent proceeds to test against nothing.
|
|
127
|
+
|
|
128
|
+
**Teardown**: Reverse order of setup. Kill processes via `tmux send-keys -t <pane_id> C-c` or kill the pane.
|
|
129
|
+
|
|
130
|
+
**Worktree isolation**: Each worktree uses unique ports (via `.env.local`), so tmux sessions in different worktrees don't conflict. Agents must use the correct repo path (`-c`) for the worktree they're operating in.
|
|
131
|
+
|
|
132
|
+
Reference xcode-automation as the canonical tmux pattern.
|
|
133
|
+
|
|
134
|
+
## Hypothesis-First Validation Workflow
|
|
135
|
+
|
|
136
|
+
New suites should be drafted, then tested hands-on on a feature branch before guidance is marked as proven. This aligns with the Proven vs Untested Guidance principle:
|
|
137
|
+
|
|
138
|
+
1. **Draft**: Write suite files based on plan and codebase analysis (mark unverified practices as hypotheses)
|
|
139
|
+
2. **Test on feature branch**: Check out a feature branch and exercise each suite's practices hands-on — boot services, run commands, verify workflows, test worktree isolation
|
|
140
|
+
3. **Verify & adjust**: Document what works, what doesn't, what needs adjustment. Worktree-specific concerns get explicit verification.
|
|
141
|
+
4. **Solidify**: Only after verification do practices become authoritative guidance. Unverified practices stay framed as motivations per the Proven vs Untested Guidance principle.
|
|
142
|
+
|
|
143
|
+
The plan/handoff document persists as the hypothesis record. If implementation runs long, it serves as the handoff document for future work.
|
|
144
|
+
|
|
145
|
+
## Cross-Referencing Between Suites
|
|
146
|
+
|
|
147
|
+
**Reference** when complex multi-step setup is involved (e.g., simulator setup spanning multiple tools) — point to the authoritative suite's decision tree rather than duplicating instructions.
|
|
148
|
+
|
|
149
|
+
**Inline** when the command is simple and stable (e.g., `xcrun simctl boot <UDID>`) — no need to send agents to another document for a single command.
|
|
150
|
+
|
|
151
|
+
Decision trees are the natural place for cross-references — branch points that route to another suite's decision tree. Example from browser-automation: "Does the change affect native iOS rendering? → Yes → follow xcode-automation decision tree for build and simulator verification."
|
|
152
|
+
|
|
153
|
+
## Testing Scenario Matrix
|
|
154
|
+
|
|
155
|
+
Target repositories should build a scenario matrix mapping their validation scenarios to suite combinations. The matrix documents which suites apply to which types of changes, so agents can quickly determine what validation is needed. Structure as a table:
|
|
156
|
+
|
|
157
|
+
| Scenario | Suite(s) | Notes |
|
|
158
|
+
|----------|----------|-------|
|
|
159
|
+
| _Description of change type_ | _Which suites apply_ | _Any special setup or cross-references_ |
|
|
160
|
+
|
|
161
|
+
Example using this repo's default suites:
|
|
162
|
+
|
|
163
|
+
| Scenario | Suite(s) | Notes |
|
|
164
|
+
|----------|----------|-------|
|
|
165
|
+
| Browser UI changes only | browser-automation | Dev server must be running |
|
|
166
|
+
| Native iOS/macOS changes | xcode-automation | Simulator setup via session defaults |
|
|
167
|
+
| Cross-platform changes (web + native) | browser-automation + xcode-automation | Each suite's decision tree routes to the relevant validation path |
|
|
168
|
+
|
|
169
|
+
When a suite serves as a shared dependency for multiple scenarios (e.g., a database management suite referenced by both API and front-end suites), it should be cross-referenced via decision tree branch points rather than duplicated.
|
|
170
|
+
|
|
171
|
+
## Environment Management Patterns
|
|
172
|
+
|
|
173
|
+
Validation suites that depend on environment configuration should document these patterns for their domain:
|
|
174
|
+
|
|
175
|
+
**ENV injection**: Document how the target project injects environment variables for different contexts (local development, testing, production). Suites should teach the pattern (e.g., "check for `.env.*` files and wrapper scripts") rather than hardcoding specific variable names.
|
|
176
|
+
|
|
177
|
+
**Service isolation**: When validation requires running services (dev servers, databases, bundlers), document how to avoid port conflicts across concurrent worktrees or parallel agent sessions. Reference the suite's ENV Configuration table for relevant variables.
|
|
178
|
+
|
|
179
|
+
**Worktree isolation**: Each worktree should use unique ports and isolated service instances where possible. Suites should document which resources need isolation and how to configure it (e.g., xcode-automation documents simulator isolation via dedicated simulator clones and derived data paths).
|
|
180
|
+
|
|
181
|
+
## Suite Creation Guidance
|
|
182
|
+
|
|
183
|
+
When creating a new validation suite for a new domain:
|
|
184
|
+
|
|
185
|
+
**Engineer provides**: Testing scenarios, tooling requirements, CI/CD integration needs, cross-references to existing suites.
|
|
186
|
+
|
|
187
|
+
**Suite author follows**:
|
|
188
|
+
1. Follow the validation suite schema (`ah schema validation-suite`)
|
|
189
|
+
2. Validate the stochastic dimension meets the existence threshold
|
|
190
|
+
3. Apply the Command Documentation Principle — external tools explicit, internal commands via patterns + discovery
|
|
191
|
+
4. Include a Decision Tree routing agents to the correct validation path
|
|
192
|
+
5. Use tmux Session Management Standard for long-running processes
|
|
193
|
+
6. Document proven vs untested guidance per the Hypothesis-First Validation Workflow
|
|
194
|
+
7. Cross-reference other suites at decision tree branch points
|
|
195
|
+
|
|
196
|
+
**Structural templates** (reference the existing default suites for patterns):
|
|
197
|
+
- xcode-automation — external-tool-heavy suite (MCP tools, xctrace, simctl). Reference for suites that primarily wrap external CLI tools with agent-driven exploration.
|
|
198
|
+
- browser-automation — dual-dimension suite (agent-browser stochastic, Playwright deterministic). Reference for suites that have both agent-driven exploration and scripted CI-gated tests.
|
|
199
|
+
|
|
83
200
|
## Related References
|
|
84
201
|
|
|
85
202
|
- [`tools-commands-mcp-hooks.md`](tools-commands-mcp-hooks.md) — When validation uses hooks, CLI commands, or MCP research tools
|
package/bin/sync-cli.js
CHANGED
|
@@ -7245,6 +7245,16 @@ function collectFilesToPush(cwd, finalIncludes, finalExcludes) {
|
|
|
7245
7245
|
const upstreamFiles = manifest.getDistributableFiles();
|
|
7246
7246
|
const filesToPush = [];
|
|
7247
7247
|
const localGitFiles = new Set(getGitFiles(cwd));
|
|
7248
|
+
const deletedFiles = /* @__PURE__ */ new Set();
|
|
7249
|
+
for (const args of [
|
|
7250
|
+
["diff", "--name-only", "--diff-filter=D"],
|
|
7251
|
+
["diff", "--cached", "--name-only", "--diff-filter=D"]
|
|
7252
|
+
]) {
|
|
7253
|
+
const result = git(args, cwd);
|
|
7254
|
+
if (result.success && result.stdout) {
|
|
7255
|
+
result.stdout.split("\n").filter(Boolean).forEach((f) => deletedFiles.add(f));
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
7248
7258
|
for (const relPath of upstreamFiles) {
|
|
7249
7259
|
if (manifest.isInitOnly(relPath)) {
|
|
7250
7260
|
continue;
|
|
@@ -7255,15 +7265,19 @@ function collectFilesToPush(cwd, finalIncludes, finalExcludes) {
|
|
|
7255
7265
|
if (finalExcludes.some((pattern) => minimatch(relPath, pattern, { dot: true }))) {
|
|
7256
7266
|
continue;
|
|
7257
7267
|
}
|
|
7258
|
-
if (!localGitFiles.has(relPath)) {
|
|
7268
|
+
if (!localGitFiles.has(relPath) && !deletedFiles.has(relPath)) {
|
|
7259
7269
|
continue;
|
|
7260
7270
|
}
|
|
7261
7271
|
const localFile = join9(cwd, relPath);
|
|
7262
7272
|
const upstreamFile = join9(allhandsRoot, relPath);
|
|
7263
|
-
if (existsSync11(localFile)
|
|
7264
|
-
if (
|
|
7265
|
-
|
|
7273
|
+
if (existsSync11(localFile)) {
|
|
7274
|
+
if (filesAreDifferent(localFile, upstreamFile)) {
|
|
7275
|
+
if (wasModifiedByTargetRepo(cwd, relPath, allhandsRoot)) {
|
|
7276
|
+
filesToPush.push({ path: relPath, type: "M" });
|
|
7277
|
+
}
|
|
7266
7278
|
}
|
|
7279
|
+
} else if (deletedFiles.has(relPath)) {
|
|
7280
|
+
filesToPush.push({ path: relPath, type: "D" });
|
|
7267
7281
|
}
|
|
7268
7282
|
}
|
|
7269
7283
|
for (const pattern of finalIncludes) {
|
|
@@ -7335,12 +7349,16 @@ async function createPullRequest(cwd, ghUser, filesToPush, title, body) {
|
|
|
7335
7349
|
console.error("Error creating branch:", checkoutResult.stderr);
|
|
7336
7350
|
return 1;
|
|
7337
7351
|
}
|
|
7338
|
-
console.log("
|
|
7352
|
+
console.log("Applying changes...");
|
|
7339
7353
|
for (const file of filesToPush) {
|
|
7340
|
-
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7354
|
+
if (file.type === "D") {
|
|
7355
|
+
git(["rm", "--ignore-unmatch", file.path], tempDir);
|
|
7356
|
+
} else {
|
|
7357
|
+
const src = join9(cwd, file.path);
|
|
7358
|
+
const dest = join9(tempDir, file.path);
|
|
7359
|
+
mkdirSync2(dirname8(dest), { recursive: true });
|
|
7360
|
+
copyFileSync2(src, dest);
|
|
7361
|
+
}
|
|
7344
7362
|
}
|
|
7345
7363
|
const addResult = git(["add", "."], tempDir);
|
|
7346
7364
|
if (!addResult.success) {
|
|
@@ -7409,8 +7427,8 @@ async function cmdPush(include, exclude, dryRun, titleArg, bodyArg) {
|
|
|
7409
7427
|
}
|
|
7410
7428
|
console.log("\nFiles to be included in PR:");
|
|
7411
7429
|
for (const file of filesToPush.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
7412
|
-
const marker = file.type
|
|
7413
|
-
const label =
|
|
7430
|
+
const marker = file.type;
|
|
7431
|
+
const label = { D: "deleted", M: "modified", A: "included" }[file.type];
|
|
7414
7432
|
console.log(` ${marker} ${file.path} (${label})`);
|
|
7415
7433
|
}
|
|
7416
7434
|
console.log();
|
package/package.json
CHANGED
package/src/commands/push.ts
CHANGED
|
@@ -17,7 +17,7 @@ interface SyncConfig {
|
|
|
17
17
|
|
|
18
18
|
interface FileEntry {
|
|
19
19
|
path: string;
|
|
20
|
-
type: 'M' | 'A';
|
|
20
|
+
type: 'M' | 'A' | 'D';
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
interface PrerequisiteResult {
|
|
@@ -124,6 +124,18 @@ function collectFilesToPush(
|
|
|
124
124
|
// Get non-ignored files in user's repo (respects .gitignore)
|
|
125
125
|
const localGitFiles = new Set(getGitFiles(cwd));
|
|
126
126
|
|
|
127
|
+
// Get files deleted locally (both staged and unstaged deletions)
|
|
128
|
+
const deletedFiles = new Set<string>();
|
|
129
|
+
for (const args of [
|
|
130
|
+
['diff', '--name-only', '--diff-filter=D'],
|
|
131
|
+
['diff', '--cached', '--name-only', '--diff-filter=D'],
|
|
132
|
+
]) {
|
|
133
|
+
const result = git(args, cwd);
|
|
134
|
+
if (result.success && result.stdout) {
|
|
135
|
+
result.stdout.split('\n').filter(Boolean).forEach((f) => deletedFiles.add(f));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
127
139
|
for (const relPath of upstreamFiles) {
|
|
128
140
|
if (manifest.isInitOnly(relPath)) {
|
|
129
141
|
continue;
|
|
@@ -134,18 +146,22 @@ function collectFilesToPush(
|
|
|
134
146
|
if (finalExcludes.some((pattern) => minimatch(relPath, pattern, { dot: true }))) {
|
|
135
147
|
continue;
|
|
136
148
|
}
|
|
137
|
-
// Skip files that are gitignored in user's repo
|
|
138
|
-
if (!localGitFiles.has(relPath)) {
|
|
149
|
+
// Skip files that are gitignored in user's repo (but allow deleted files through)
|
|
150
|
+
if (!localGitFiles.has(relPath) && !deletedFiles.has(relPath)) {
|
|
139
151
|
continue;
|
|
140
152
|
}
|
|
141
153
|
|
|
142
154
|
const localFile = join(cwd, relPath);
|
|
143
155
|
const upstreamFile = join(allhandsRoot, relPath);
|
|
144
156
|
|
|
145
|
-
if (existsSync(localFile)
|
|
146
|
-
if (
|
|
147
|
-
|
|
157
|
+
if (existsSync(localFile)) {
|
|
158
|
+
if (filesAreDifferent(localFile, upstreamFile)) {
|
|
159
|
+
if (wasModifiedByTargetRepo(cwd, relPath, allhandsRoot)) {
|
|
160
|
+
filesToPush.push({ path: relPath, type: 'M' });
|
|
161
|
+
}
|
|
148
162
|
}
|
|
163
|
+
} else if (deletedFiles.has(relPath)) {
|
|
164
|
+
filesToPush.push({ path: relPath, type: 'D' });
|
|
149
165
|
}
|
|
150
166
|
}
|
|
151
167
|
|
|
@@ -239,12 +255,16 @@ async function createPullRequest(
|
|
|
239
255
|
return 1;
|
|
240
256
|
}
|
|
241
257
|
|
|
242
|
-
console.log('
|
|
258
|
+
console.log('Applying changes...');
|
|
243
259
|
for (const file of filesToPush) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
260
|
+
if (file.type === 'D') {
|
|
261
|
+
git(['rm', '--ignore-unmatch', file.path], tempDir);
|
|
262
|
+
} else {
|
|
263
|
+
const src = join(cwd, file.path);
|
|
264
|
+
const dest = join(tempDir, file.path);
|
|
265
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
266
|
+
copyFileSync(src, dest);
|
|
267
|
+
}
|
|
248
268
|
}
|
|
249
269
|
|
|
250
270
|
const addResult = git(['add', '.'], tempDir);
|
|
@@ -328,8 +348,8 @@ export async function cmdPush(
|
|
|
328
348
|
|
|
329
349
|
console.log('\nFiles to be included in PR:');
|
|
330
350
|
for (const file of filesToPush.sort((a, b) => a.path.localeCompare(b.path))) {
|
|
331
|
-
const marker = file.type
|
|
332
|
-
const label =
|
|
351
|
+
const marker = file.type;
|
|
352
|
+
const label = { D: 'deleted', M: 'modified', A: 'included' }[file.type];
|
|
333
353
|
console.log(` ${marker} ${file.path} (${label})`);
|
|
334
354
|
}
|
|
335
355
|
console.log();
|