@caupulican/pi-adaptative 0.84.1 → 0.85.3
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/CHANGELOG.md +53 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +14 -8
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +14 -8
- package/dist/bundled-resources/runtimes/pi-shell-engine/arithmetic.py +364 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +2 -1
- package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +16 -0
- package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +137 -14
- package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +23 -2
- package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +134 -6
- package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +25 -2
- package/dist/bundled-resources/skills/deduplicate-by-evidence/SKILL.md +7 -5
- package/dist/bundled-resources/skills/worker-profile-writer/SKILL.md +63 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +8 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
- package/dist/core/autonomy/lane-tool-surface.js +4 -2
- package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +3 -0
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +6 -0
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/compaction-controller.d.ts +2 -0
- package/dist/core/compaction-controller.d.ts.map +1 -1
- package/dist/core/compaction-controller.js +35 -13
- package/dist/core/compaction-controller.js.map +1 -1
- package/dist/core/default-tool-surface.d.ts.map +1 -1
- package/dist/core/default-tool-surface.js +1 -0
- package/dist/core/default-tool-surface.js.map +1 -1
- package/dist/core/delegation/worker-delegation-controller.d.ts +7 -0
- package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -1
- package/dist/core/delegation/worker-delegation-controller.js +28 -0
- package/dist/core/delegation/worker-delegation-controller.js.map +1 -1
- package/dist/core/delegation/worker-profile-resolver.d.ts +2 -0
- package/dist/core/delegation/worker-profile-resolver.d.ts.map +1 -1
- package/dist/core/delegation/worker-profile-resolver.js +46 -11
- package/dist/core/delegation/worker-profile-resolver.js.map +1 -1
- package/dist/core/doctor.d.ts +1 -1
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +5 -4
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/failure-corpus.d.ts +26 -1
- package/dist/core/failure-corpus.d.ts.map +1 -1
- package/dist/core/failure-corpus.js +17 -0
- package/dist/core/failure-corpus.js.map +1 -1
- package/dist/core/model-capability.d.ts.map +1 -1
- package/dist/core/model-capability.js +1 -0
- package/dist/core/model-capability.js.map +1 -1
- package/dist/core/model-tool-protocol.d.ts +3 -2
- package/dist/core/model-tool-protocol.d.ts.map +1 -1
- package/dist/core/model-tool-protocol.js +5 -1
- package/dist/core/model-tool-protocol.js.map +1 -1
- package/dist/core/models/runtime-process.d.ts +7 -1
- package/dist/core/models/runtime-process.d.ts.map +1 -1
- package/dist/core/models/runtime-process.js +4 -2
- package/dist/core/models/runtime-process.js.map +1 -1
- package/dist/core/orchestration/lane-tool-manifests.d.ts +1 -1
- package/dist/core/orchestration/lane-tool-manifests.d.ts.map +1 -1
- package/dist/core/orchestration/lane-tool-manifests.js +6 -1
- package/dist/core/orchestration/lane-tool-manifests.js.map +1 -1
- package/dist/core/orchestration/session-task-profile-store.d.ts +31 -0
- package/dist/core/orchestration/session-task-profile-store.d.ts.map +1 -0
- package/dist/core/orchestration/session-task-profile-store.js +138 -0
- package/dist/core/orchestration/session-task-profile-store.js.map +1 -0
- package/dist/core/orchestration/task-profile-writer.d.ts +58 -0
- package/dist/core/orchestration/task-profile-writer.d.ts.map +1 -0
- package/dist/core/orchestration/task-profile-writer.js +165 -0
- package/dist/core/orchestration/task-profile-writer.js.map +1 -0
- package/dist/core/process-matrix/runtime.js +43 -11
- package/dist/core/process-matrix/runtime.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +18 -6
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +3 -1
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +14 -0
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/security/untrusted-boundary.d.ts.map +1 -1
- package/dist/core/security/untrusted-boundary.js +4 -6
- package/dist/core/security/untrusted-boundary.js.map +1 -1
- package/dist/core/session-role.d.ts.map +1 -1
- package/dist/core/session-role.js +1 -0
- package/dist/core/session-role.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +21 -23
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tool-capability-policy.js +1 -1
- package/dist/core/tool-capability-policy.js.map +1 -1
- package/dist/core/tool-recovery-log-records.d.ts +33 -1
- package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
- package/dist/core/tool-recovery-log-records.js +59 -12
- package/dist/core/tool-recovery-log-records.js.map +1 -1
- package/dist/core/tool-recovery-logger.d.ts +12 -1
- package/dist/core/tool-recovery-logger.d.ts.map +1 -1
- package/dist/core/tool-recovery-logger.js +24 -1
- package/dist/core/tool-recovery-logger.js.map +1 -1
- package/dist/core/tool-repair-health.d.ts.map +1 -1
- package/dist/core/tool-repair-health.js +6 -0
- package/dist/core/tool-repair-health.js.map +1 -1
- package/dist/core/tools/bash.d.ts +3 -8
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +4 -28
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/delegate.js +4 -4
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +32 -5
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +137 -69
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +22 -7
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +128 -55
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-intent.d.ts +70 -0
- package/dist/core/tools/file-mutation-intent.d.ts.map +1 -0
- package/dist/core/tools/file-mutation-intent.js +325 -0
- package/dist/core/tools/file-mutation-intent.js.map +1 -0
- package/dist/core/tools/find.d.ts +2 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +2 -6
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/goal.d.ts.map +1 -1
- package/dist/core/tools/goal.js +41 -18
- package/dist/core/tools/goal.js.map +1 -1
- package/dist/core/tools/grep.d.ts +2 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +2 -6
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +3 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +31 -8
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/managed-search-tool.d.ts +8 -0
- package/dist/core/tools/managed-search-tool.d.ts.map +1 -0
- package/dist/core/tools/managed-search-tool.js +10 -0
- package/dist/core/tools/managed-search-tool.js.map +1 -0
- package/dist/core/tools/profile-writer.d.ts +33 -0
- package/dist/core/tools/profile-writer.d.ts.map +1 -0
- package/dist/core/tools/profile-writer.js +67 -0
- package/dist/core/tools/profile-writer.js.map +1 -0
- package/dist/core/tools/shell-command-parser.d.ts +4 -3
- package/dist/core/tools/shell-command-parser.d.ts.map +1 -1
- package/dist/core/tools/shell-command-parser.js +32 -7
- package/dist/core/tools/shell-command-parser.js.map +1 -1
- package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
- package/dist/core/tools/shell-contract-router.js +23 -1
- package/dist/core/tools/shell-contract-router.js.map +1 -1
- package/dist/core/tools/task-steps.d.ts.map +1 -1
- package/dist/core/tools/task-steps.js +6 -11
- package/dist/core/tools/task-steps.js.map +1 -1
- package/dist/core/tools/tmux-dispatch.d.ts +2 -0
- package/dist/core/tools/tmux-dispatch.d.ts.map +1 -1
- package/dist/core/tools/tmux-dispatch.js +16 -2
- package/dist/core/tools/tmux-dispatch.js.map +1 -1
- package/dist/core/tools/windows-shell-engine.js +1 -1
- package/dist/core/tools/windows-shell-engine.js.map +1 -1
- package/dist/core/tools/write.d.ts +24 -7
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +182 -107
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/jscpd-cli.d.ts +3 -0
- package/dist/jscpd-cli.d.ts.map +1 -0
- package/dist/jscpd-cli.js +22 -0
- package/dist/jscpd-cli.js.map +1 -0
- package/dist/modes/interactive/components/profile-resource-editor.js +2 -2
- package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +85 -3
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/bundled-jscpd.d.ts +20 -0
- package/dist/utils/bundled-jscpd.d.ts.map +1 -0
- package/dist/utils/bundled-jscpd.js +154 -0
- package/dist/utils/bundled-jscpd.js.map +1 -0
- package/dist/utils/paths.js +1 -1
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +11 -0
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +19 -2
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +92 -40
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/index.md +3 -0
- package/docs/managed-data-tools.md +15 -0
- package/docs/models.md +1 -1
- package/docs/quickstart.md +2 -2
- package/docs/settings.md +1 -1
- package/docs/tool-boundary-performance-roadmap-2026-08-02.md +166 -0
- package/docs/tool-repair.md +4 -4
- package/docs/windows.md +7 -5
- package/docs/worker-profiles.md +16 -0
- package/examples/extensions/built-in-tool-renderer.ts +12 -5
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +8 -7
- package/examples/extensions/prompt-customizer.ts +4 -2
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/ssh.ts +113 -24
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +112 -12
- package/package.json +7 -5
|
@@ -10,17 +10,20 @@ import io
|
|
|
10
10
|
import os
|
|
11
11
|
import subprocess
|
|
12
12
|
import threading
|
|
13
|
+
import time
|
|
13
14
|
from typing import BinaryIO
|
|
14
15
|
|
|
16
|
+
from arithmetic import ArithmeticError, compile_arithmetic, evaluate_arithmetic
|
|
15
17
|
import nodes
|
|
16
18
|
import parser as parser_module
|
|
17
19
|
import proc
|
|
18
20
|
import tokens as tokens_module
|
|
19
21
|
from context import RUNNER_BUILTINS, STATE_BUILTINS, BuiltinContext, ExecContext
|
|
20
|
-
from errors import ShellExit, UnsupportedConstruct
|
|
22
|
+
from errors import LoopBreak, LoopContinue, LoopControl, ShellExit, UnsupportedConstruct
|
|
21
23
|
from state import ShellState
|
|
22
24
|
|
|
23
25
|
DEVNULL = os.devnull
|
|
26
|
+
MAX_LOOP_ITERATIONS = 1_000_000
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
class _Redirected:
|
|
@@ -344,7 +347,7 @@ def _run_pipeline_elements(elements: list, ctx: ExecContext) -> int:
|
|
|
344
347
|
try:
|
|
345
348
|
# Pipeline stages are subshell-like: state mutations belong to the stage,
|
|
346
349
|
# never to the parent command context or a sibling stage.
|
|
347
|
-
stage_ctx = _sub_ctx(ctx, ctx.state.copy(), sin, sout, stderr_base)
|
|
350
|
+
stage_ctx = _sub_ctx(ctx, ctx.state.copy(), sin, sout, stderr_base, loop_depth=0)
|
|
348
351
|
results[idx] = _dispatch_element(elements[idx], stage_ctx, sin, sout, stderr_base, is_pipeline=True)
|
|
349
352
|
except UnsupportedConstruct as exc:
|
|
350
353
|
# A structured refusal from an inline stage means the WHOLE command
|
|
@@ -356,6 +359,10 @@ def _run_pipeline_elements(elements: list, ctx: ExecContext) -> int:
|
|
|
356
359
|
results[idx] = 1
|
|
357
360
|
except ShellExit as exc:
|
|
358
361
|
results[idx] = exc.exit_code
|
|
362
|
+
except LoopControl:
|
|
363
|
+
# A pipeline stage is a subshell boundary. Loop control in that
|
|
364
|
+
# stage cannot alter an enclosing loop in the parent context.
|
|
365
|
+
results[idx] = 0
|
|
359
366
|
except BaseException as exc: # noqa: BLE001 - any other stage failure must not crash the engine
|
|
360
367
|
# Any OTHER exception is a real per-stage failure, not a refusal: it
|
|
361
368
|
# gets a named, actionable one-line message on the merged sink (never
|
|
@@ -487,6 +494,8 @@ def _dispatch_element(
|
|
|
487
494
|
return _execute_subshell(element, ctx, stdin_stream, stdout_stream, stderr_stream)
|
|
488
495
|
if isinstance(element, nodes.BraceGroup):
|
|
489
496
|
return _execute_brace_group(element, ctx, stdin_stream, stdout_stream, stderr_stream)
|
|
497
|
+
if isinstance(element, (nodes.ForCommand, nodes.ArithmeticForCommand)):
|
|
498
|
+
return _execute_for_command(element, ctx, stdin_stream, stdout_stream, stderr_stream)
|
|
490
499
|
if isinstance(element, nodes.SimpleCommand):
|
|
491
500
|
return _dispatch_simple_command(element, ctx, stdin_stream, stdout_stream, stderr_stream)
|
|
492
501
|
raise UnsupportedConstruct("malformed-syntax", f"unrecognized pipeline element {type(element)!r}")
|
|
@@ -498,6 +507,8 @@ def _sub_ctx(
|
|
|
498
507
|
stdin_stream: BinaryIO | int,
|
|
499
508
|
stdout_stream: BinaryIO | int,
|
|
500
509
|
stderr_stream: BinaryIO | int,
|
|
510
|
+
*,
|
|
511
|
+
loop_depth: int | None = None,
|
|
501
512
|
) -> ExecContext:
|
|
502
513
|
return ExecContext(
|
|
503
514
|
state=state,
|
|
@@ -508,11 +519,12 @@ def _sub_ctx(
|
|
|
508
519
|
builtins=ctx.builtins,
|
|
509
520
|
deadline=ctx.deadline,
|
|
510
521
|
stderr=stderr_stream,
|
|
522
|
+
loop_depth=ctx.loop_depth if loop_depth is None else loop_depth,
|
|
511
523
|
)
|
|
512
524
|
|
|
513
525
|
|
|
514
|
-
def
|
|
515
|
-
node: nodes.Subshell | nodes.BraceGroup,
|
|
526
|
+
def _execute_redirected_compound(
|
|
527
|
+
node: nodes.Subshell | nodes.BraceGroup | nodes.ForCommand | nodes.ArithmeticForCommand,
|
|
516
528
|
ctx: ExecContext,
|
|
517
529
|
stdin_stream,
|
|
518
530
|
stdout_stream,
|
|
@@ -526,8 +538,18 @@ def _execute_redirected_group(
|
|
|
526
538
|
node.redirects, ctx, stdin_stream, stdout_stream, stderr_stream, tracker
|
|
527
539
|
)
|
|
528
540
|
state = ctx.state.copy() if isolated else ctx.state
|
|
529
|
-
inner_ctx = _sub_ctx(
|
|
541
|
+
inner_ctx = _sub_ctx(
|
|
542
|
+
ctx,
|
|
543
|
+
state,
|
|
544
|
+
r_in,
|
|
545
|
+
r_out,
|
|
546
|
+
r_err,
|
|
547
|
+
loop_depth=0 if isolated else ctx.loop_depth,
|
|
548
|
+
)
|
|
530
549
|
try:
|
|
550
|
+
if isinstance(node, (nodes.ForCommand, nodes.ArithmeticForCommand)):
|
|
551
|
+
inner_ctx.loop_depth += 1
|
|
552
|
+
return _execute_loop(node, inner_ctx)
|
|
531
553
|
return execute(node.body, inner_ctx)
|
|
532
554
|
except ShellExit as exc:
|
|
533
555
|
if isolated:
|
|
@@ -538,17 +560,89 @@ def _execute_redirected_group(
|
|
|
538
560
|
|
|
539
561
|
|
|
540
562
|
def _execute_subshell(node: nodes.Subshell, ctx: ExecContext, stdin_stream, stdout_stream, stderr_stream) -> int:
|
|
541
|
-
return
|
|
563
|
+
return _execute_redirected_compound(
|
|
542
564
|
node, ctx, stdin_stream, stdout_stream, stderr_stream, isolated=True
|
|
543
565
|
)
|
|
544
566
|
|
|
545
567
|
|
|
546
568
|
def _execute_brace_group(node: nodes.BraceGroup, ctx: ExecContext, stdin_stream, stdout_stream, stderr_stream) -> int:
|
|
547
|
-
return
|
|
569
|
+
return _execute_redirected_compound(
|
|
570
|
+
node, ctx, stdin_stream, stdout_stream, stderr_stream, isolated=False
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def _execute_for_command(
|
|
575
|
+
node: nodes.ForCommand | nodes.ArithmeticForCommand,
|
|
576
|
+
ctx: ExecContext,
|
|
577
|
+
stdin_stream,
|
|
578
|
+
stdout_stream,
|
|
579
|
+
stderr_stream,
|
|
580
|
+
) -> int:
|
|
581
|
+
return _execute_redirected_compound(
|
|
548
582
|
node, ctx, stdin_stream, stdout_stream, stderr_stream, isolated=False
|
|
549
583
|
)
|
|
550
584
|
|
|
551
585
|
|
|
586
|
+
def _check_loop_budget(ctx: ExecContext, iteration: int) -> None:
|
|
587
|
+
if ctx.deadline is not None and time.monotonic() >= ctx.deadline:
|
|
588
|
+
raise ShellExit(124)
|
|
589
|
+
if iteration >= MAX_LOOP_ITERATIONS:
|
|
590
|
+
_write_merged(
|
|
591
|
+
_merged_sink(ctx),
|
|
592
|
+
f"for: iteration limit ({MAX_LOOP_ITERATIONS}) exceeded\n".encode("utf-8"),
|
|
593
|
+
ctx,
|
|
594
|
+
)
|
|
595
|
+
raise ShellExit(124)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def _execute_loop_body(body: nodes.CommandList, ctx: ExecContext) -> tuple[int, str | None]:
|
|
599
|
+
try:
|
|
600
|
+
return execute(body, ctx), None
|
|
601
|
+
except LoopBreak as exc:
|
|
602
|
+
if exc.levels > 1:
|
|
603
|
+
raise LoopBreak(exc.levels - 1) from None
|
|
604
|
+
return 0, "break"
|
|
605
|
+
except LoopContinue as exc:
|
|
606
|
+
if exc.levels > 1:
|
|
607
|
+
raise LoopContinue(exc.levels - 1) from None
|
|
608
|
+
return 0, "continue"
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def _execute_loop(node: nodes.ForCommand | nodes.ArithmeticForCommand, ctx: ExecContext) -> int:
|
|
612
|
+
if isinstance(node, nodes.ForCommand):
|
|
613
|
+
values: list[str] = []
|
|
614
|
+
for item in node.items:
|
|
615
|
+
values.extend(ctx.expand_word(item, ctx))
|
|
616
|
+
|
|
617
|
+
exit_code = 0
|
|
618
|
+
for iteration, value in enumerate(values):
|
|
619
|
+
_check_loop_budget(ctx, iteration)
|
|
620
|
+
ctx.state.setenv(node.name, value)
|
|
621
|
+
exit_code, action = _execute_loop_body(node.body, ctx)
|
|
622
|
+
if action == "break":
|
|
623
|
+
return exit_code
|
|
624
|
+
return exit_code
|
|
625
|
+
|
|
626
|
+
try:
|
|
627
|
+
initializer = compile_arithmetic(node.initializer)
|
|
628
|
+
condition = compile_arithmetic(node.condition)
|
|
629
|
+
update = compile_arithmetic(node.update)
|
|
630
|
+
evaluate_arithmetic(initializer, ctx.state)
|
|
631
|
+
exit_code = 0
|
|
632
|
+
iteration = 0
|
|
633
|
+
while condition is None or evaluate_arithmetic(condition, ctx.state) != 0:
|
|
634
|
+
_check_loop_budget(ctx, iteration)
|
|
635
|
+
iteration += 1
|
|
636
|
+
exit_code, action = _execute_loop_body(node.body, ctx)
|
|
637
|
+
if action == "break":
|
|
638
|
+
return exit_code
|
|
639
|
+
evaluate_arithmetic(update, ctx.state)
|
|
640
|
+
return exit_code
|
|
641
|
+
except ArithmeticError as exc:
|
|
642
|
+
_write_merged(_merged_sink(ctx), f"bash: arithmetic: {exc}\n".encode("utf-8"), ctx)
|
|
643
|
+
return 1
|
|
644
|
+
|
|
645
|
+
|
|
552
646
|
def _dispatch_simple_command(
|
|
553
647
|
command: nodes.SimpleCommand,
|
|
554
648
|
ctx: ExecContext,
|
|
@@ -577,7 +671,7 @@ def _dispatch_simple_command(
|
|
|
577
671
|
name = argv[0]
|
|
578
672
|
|
|
579
673
|
if name in STATE_BUILTINS:
|
|
580
|
-
return _run_state_builtin(name, argv, ctx, r_out)
|
|
674
|
+
return _run_state_builtin(name, argv, ctx, r_out, r_err)
|
|
581
675
|
|
|
582
676
|
if name in RUNNER_BUILTINS:
|
|
583
677
|
return _run_xargs(argv, ctx, r_in, r_out)
|
|
@@ -621,17 +715,46 @@ def _as_stream(fd_or_stream: BinaryIO | int, mode: str) -> BinaryIO:
|
|
|
621
715
|
return fd_or_stream
|
|
622
716
|
|
|
623
717
|
|
|
624
|
-
def _run_state_builtin(
|
|
718
|
+
def _run_state_builtin(
|
|
719
|
+
name: str,
|
|
720
|
+
argv: list[str],
|
|
721
|
+
ctx: ExecContext,
|
|
722
|
+
out_stream: BinaryIO | int,
|
|
723
|
+
error_stream: BinaryIO | int | None = None,
|
|
724
|
+
) -> int:
|
|
725
|
+
diagnostic_stream = error_stream if error_stream is not None else out_stream
|
|
726
|
+
if name in ("break", "continue"):
|
|
727
|
+
if len(argv) > 2:
|
|
728
|
+
_write_merged(diagnostic_stream, f"{name}: too many arguments\n".encode("utf-8"), ctx)
|
|
729
|
+
return 1
|
|
730
|
+
try:
|
|
731
|
+
levels = int(argv[1], 10) if len(argv) == 2 else 1
|
|
732
|
+
except ValueError:
|
|
733
|
+
_write_merged(diagnostic_stream, f"{name}: {argv[1]}: numeric argument required\n".encode("utf-8"), ctx)
|
|
734
|
+
return 1
|
|
735
|
+
if levels <= 0:
|
|
736
|
+
_write_merged(diagnostic_stream, f"{name}: loop count must be positive\n".encode("utf-8"), ctx)
|
|
737
|
+
return 1
|
|
738
|
+
if ctx.loop_depth == 0:
|
|
739
|
+
_write_merged(
|
|
740
|
+
diagnostic_stream,
|
|
741
|
+
f"bash: {name}: only meaningful in a 'for', 'while', or 'until' loop\n".encode("utf-8"),
|
|
742
|
+
ctx,
|
|
743
|
+
)
|
|
744
|
+
return 0
|
|
745
|
+
control = LoopBreak if name == "break" else LoopContinue
|
|
746
|
+
raise control(min(levels, ctx.loop_depth))
|
|
747
|
+
|
|
625
748
|
if name == "exit":
|
|
626
749
|
if len(argv) > 2:
|
|
627
|
-
_write_merged(
|
|
750
|
+
_write_merged(diagnostic_stream, b"exit: too many arguments\n", ctx)
|
|
628
751
|
raise ShellExit(1)
|
|
629
752
|
if len(argv) == 1:
|
|
630
753
|
raise ShellExit(ctx.state.last_exit_code)
|
|
631
754
|
try:
|
|
632
755
|
exit_code = int(argv[1], 10)
|
|
633
756
|
except ValueError:
|
|
634
|
-
_write_merged(
|
|
757
|
+
_write_merged(diagnostic_stream, f"exit: {argv[1]}: numeric argument required\n".encode("utf-8"), ctx)
|
|
635
758
|
raise ShellExit(2)
|
|
636
759
|
raise ShellExit(exit_code & 0xFF)
|
|
637
760
|
|
|
@@ -641,16 +764,16 @@ def _run_state_builtin(name: str, argv: list[str], ctx: ExecContext, out_stream:
|
|
|
641
764
|
if print_new_cwd:
|
|
642
765
|
oldpwd = ctx.state.env.get("OLDPWD")
|
|
643
766
|
if not oldpwd:
|
|
644
|
-
_write_merged(
|
|
767
|
+
_write_merged(diagnostic_stream, b"cd: OLDPWD not set\n", ctx)
|
|
645
768
|
return 1
|
|
646
769
|
target = oldpwd
|
|
647
770
|
if target is None:
|
|
648
|
-
_write_merged(
|
|
771
|
+
_write_merged(diagnostic_stream, b"cd: HOME not set\n", ctx)
|
|
649
772
|
return 1
|
|
650
773
|
try:
|
|
651
774
|
ctx.state.chdir(target)
|
|
652
775
|
except FileNotFoundError:
|
|
653
|
-
_write_merged(
|
|
776
|
+
_write_merged(diagnostic_stream, f"cd: {target}: No such file or directory\n".encode("utf-8"), ctx)
|
|
654
777
|
return 1
|
|
655
778
|
if print_new_cwd:
|
|
656
779
|
_write_merged(out_stream, f"{ctx.state.cwd}\n".encode("utf-8"), ctx)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""AST dataclasses for the pi shell engine: word segments + command structure.
|
|
2
2
|
|
|
3
3
|
Transcribed verbatim from windows-shell-workpackages-2026-07-19.md §1.4. Type tags for
|
|
4
4
|
tests come from `type(node).__name__`; do NOT add a `kind` field to any node.
|
|
@@ -95,7 +95,28 @@ class BraceGroup:
|
|
|
95
95
|
redirects: list["Redirect"]
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
@dataclass
|
|
99
|
+
class ForCommand:
|
|
100
|
+
"""POSIX word-list loop, including the omitted-``in`` positional form."""
|
|
101
|
+
|
|
102
|
+
name: str
|
|
103
|
+
items: list["Word"]
|
|
104
|
+
body: "CommandList"
|
|
105
|
+
redirects: list["Redirect"]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class ArithmeticForCommand:
|
|
110
|
+
"""Bash arithmetic loop: ``for ((init; condition; update)); do ...; done``."""
|
|
111
|
+
|
|
112
|
+
initializer: str
|
|
113
|
+
condition: str
|
|
114
|
+
update: str
|
|
115
|
+
body: "CommandList"
|
|
116
|
+
redirects: list["Redirect"]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
PipelineElement = SimpleCommand | Subshell | BraceGroup | ForCommand | ArithmeticForCommand
|
|
99
120
|
|
|
100
121
|
|
|
101
122
|
@dataclass
|
|
@@ -13,9 +13,11 @@ import re
|
|
|
13
13
|
from errors import UnsupportedConstruct
|
|
14
14
|
from nodes import (
|
|
15
15
|
AndOr,
|
|
16
|
+
ArithmeticForCommand,
|
|
16
17
|
BraceGroup,
|
|
17
18
|
CommandList,
|
|
18
19
|
DQ,
|
|
20
|
+
ForCommand,
|
|
19
21
|
Lit,
|
|
20
22
|
Pipeline,
|
|
21
23
|
PipelineElement,
|
|
@@ -28,11 +30,12 @@ from nodes import (
|
|
|
28
30
|
from tokens import Token
|
|
29
31
|
|
|
30
32
|
_ASSIGNMENT_RE = re.compile(r"^([A-Za-z_][A-Za-z0-9_]*)=(.*)$", re.DOTALL)
|
|
33
|
+
_IDENTIFIER_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
|
|
31
34
|
_EXTGLOB_RE = re.compile(r"[@!?*+]\(")
|
|
32
35
|
_BRACE_EXPANSION_RE = re.compile(r"\{[^{}]*,[^{}]*\}")
|
|
33
36
|
|
|
34
37
|
_JOB_CONTROL_WORDS = {"fg", "bg", "jobs", "wait", "disown"}
|
|
35
|
-
_CONTROL_FLOW_WORDS = {"if", "
|
|
38
|
+
_CONTROL_FLOW_WORDS = {"if", "while", "until", "case", "select"}
|
|
36
39
|
_ARITHMETIC_WORDS = {"let"}
|
|
37
40
|
_UNSUPPORTED_BUILTIN_WORDS = {"eval", "source", ".", "alias", "trap", "set", "shopt", "read", "declare", "local"}
|
|
38
41
|
_NESTED_SHELL_WORDS = {"bash", "sh", "zsh", "fish", "cmd", "powershell", "pwsh", "wsl"}
|
|
@@ -124,26 +127,47 @@ class _Parser:
|
|
|
124
127
|
tok = self.peek()
|
|
125
128
|
return tok is not None and tok.kind == "OP" and tok.text == text
|
|
126
129
|
|
|
130
|
+
def at_unquoted_word(self, text: str) -> bool:
|
|
131
|
+
tok = self.peek()
|
|
132
|
+
return (
|
|
133
|
+
tok is not None
|
|
134
|
+
and tok.kind == "WORD"
|
|
135
|
+
and tok.segments is not None
|
|
136
|
+
and len(tok.segments) == 1
|
|
137
|
+
and isinstance(tok.segments[0], Raw)
|
|
138
|
+
and tok.segments[0].text == text
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def _at_stop(self, stop_texts: frozenset[str], stop_words: frozenset[str]) -> bool:
|
|
142
|
+
tok = self.peek()
|
|
143
|
+
if tok is None:
|
|
144
|
+
return True
|
|
145
|
+
if tok.kind == "OP":
|
|
146
|
+
return tok.text in stop_texts
|
|
147
|
+
return any(self.at_unquoted_word(word) for word in stop_words)
|
|
148
|
+
|
|
127
149
|
def _skip_seps(self) -> None:
|
|
128
150
|
while self.at_op(";") or self.at_op("\n"):
|
|
129
151
|
self.advance()
|
|
130
152
|
|
|
131
|
-
def parse_command_list(
|
|
153
|
+
def parse_command_list(
|
|
154
|
+
self, stop_texts: frozenset[str], stop_words: frozenset[str] = frozenset()
|
|
155
|
+
) -> CommandList:
|
|
132
156
|
entries = []
|
|
133
157
|
separators: list[str] = []
|
|
134
158
|
self._skip_seps()
|
|
135
159
|
while True:
|
|
136
|
-
|
|
137
|
-
if tok is None or (tok.kind == "OP" and tok.text in stop_texts):
|
|
160
|
+
if self._at_stop(stop_texts, stop_words):
|
|
138
161
|
break
|
|
139
162
|
entries.append(self.parse_and_or())
|
|
140
163
|
if self.at_op("&"):
|
|
141
164
|
raise UnsupportedConstruct(
|
|
142
165
|
"job-control", "Background execution ('&') is not supported; run commands synchronously."
|
|
143
166
|
)
|
|
144
|
-
|
|
145
|
-
if tok is None or (tok.kind == "OP" and tok.text in stop_texts):
|
|
167
|
+
if self._at_stop(stop_texts, stop_words):
|
|
146
168
|
break
|
|
169
|
+
tok = self.peek()
|
|
170
|
+
assert tok is not None
|
|
147
171
|
if tok.kind == "OP" and tok.text in (";", "\n"):
|
|
148
172
|
separators.append(self.advance().text) # type: ignore[arg-type]
|
|
149
173
|
self._skip_seps()
|
|
@@ -194,6 +218,13 @@ class _Parser:
|
|
|
194
218
|
self.advance()
|
|
195
219
|
redirects = self._parse_redirects()
|
|
196
220
|
return BraceGroup(body=body, redirects=redirects)
|
|
221
|
+
if self.at_unquoted_word("for"):
|
|
222
|
+
return self.parse_for_command()
|
|
223
|
+
if self.peek() is not None and self.peek().kind == "ARITH":
|
|
224
|
+
raise UnsupportedConstruct(
|
|
225
|
+
"arithmetic-expansion",
|
|
226
|
+
"Arithmetic commands '((...))' are supported only as a for-loop header.",
|
|
227
|
+
)
|
|
197
228
|
command = self.parse_simple_command()
|
|
198
229
|
if not command.assignments and not command.words and not command.redirects:
|
|
199
230
|
# A fully empty SimpleCommand reached as a pipeline element means a missing
|
|
@@ -204,6 +235,103 @@ class _Parser:
|
|
|
204
235
|
)
|
|
205
236
|
return command
|
|
206
237
|
|
|
238
|
+
def _parse_for_body(self) -> CommandList:
|
|
239
|
+
if not (self.at_op(";") or self.at_op("\n")):
|
|
240
|
+
raise UnsupportedConstruct(
|
|
241
|
+
"malformed-syntax", "A for loop requires ';' or a newline before 'do'."
|
|
242
|
+
)
|
|
243
|
+
self._skip_seps()
|
|
244
|
+
if not self.at_unquoted_word("do"):
|
|
245
|
+
raise UnsupportedConstruct("malformed-syntax", "A for loop is missing its 'do' keyword.")
|
|
246
|
+
self.advance()
|
|
247
|
+
|
|
248
|
+
body = self.parse_command_list(frozenset(), frozenset({"done"}))
|
|
249
|
+
if not self.at_unquoted_word("done"):
|
|
250
|
+
raise UnsupportedConstruct("malformed-syntax", "A for loop is missing its closing 'done' keyword.")
|
|
251
|
+
if not body.entries:
|
|
252
|
+
raise UnsupportedConstruct("malformed-syntax", "A for loop requires at least one command in its body.")
|
|
253
|
+
self.advance()
|
|
254
|
+
return body
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
def _split_arithmetic_header(header: str) -> tuple[str, str, str]:
|
|
258
|
+
parts: list[str] = []
|
|
259
|
+
start = 0
|
|
260
|
+
depth = 0
|
|
261
|
+
for index, char in enumerate(header):
|
|
262
|
+
if char == "(":
|
|
263
|
+
depth += 1
|
|
264
|
+
elif char == ")":
|
|
265
|
+
depth -= 1
|
|
266
|
+
if depth < 0:
|
|
267
|
+
raise UnsupportedConstruct(
|
|
268
|
+
"malformed-syntax", "Arithmetic for header contains an unmatched ')'."
|
|
269
|
+
)
|
|
270
|
+
elif char == ";" and depth == 0:
|
|
271
|
+
parts.append(header[start:index].strip())
|
|
272
|
+
start = index + 1
|
|
273
|
+
if depth != 0:
|
|
274
|
+
raise UnsupportedConstruct(
|
|
275
|
+
"malformed-syntax", "Arithmetic for header contains unbalanced parentheses."
|
|
276
|
+
)
|
|
277
|
+
parts.append(header[start:].strip())
|
|
278
|
+
if len(parts) != 3:
|
|
279
|
+
raise UnsupportedConstruct(
|
|
280
|
+
"malformed-syntax",
|
|
281
|
+
"An arithmetic for loop requires exactly 'initializer; condition; update'.",
|
|
282
|
+
)
|
|
283
|
+
return parts[0], parts[1], parts[2]
|
|
284
|
+
|
|
285
|
+
def parse_for_command(self) -> ForCommand | ArithmeticForCommand:
|
|
286
|
+
self.advance() # `for`
|
|
287
|
+
arithmetic = self.peek()
|
|
288
|
+
if arithmetic is not None and arithmetic.kind == "ARITH":
|
|
289
|
+
self.advance()
|
|
290
|
+
initializer, condition, update = self._split_arithmetic_header(arithmetic.text or "")
|
|
291
|
+
body = self._parse_for_body()
|
|
292
|
+
return ArithmeticForCommand(
|
|
293
|
+
initializer=initializer,
|
|
294
|
+
condition=condition,
|
|
295
|
+
update=update,
|
|
296
|
+
body=body,
|
|
297
|
+
redirects=self._parse_redirects(),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
variable = self.peek()
|
|
301
|
+
variable_name = (
|
|
302
|
+
variable.segments[0].text
|
|
303
|
+
if variable is not None
|
|
304
|
+
and variable.kind == "WORD"
|
|
305
|
+
and variable.segments is not None
|
|
306
|
+
and len(variable.segments) == 1
|
|
307
|
+
and isinstance(variable.segments[0], Raw)
|
|
308
|
+
else ""
|
|
309
|
+
)
|
|
310
|
+
if not _IDENTIFIER_RE.fullmatch(variable_name):
|
|
311
|
+
raise UnsupportedConstruct(
|
|
312
|
+
"malformed-syntax", "A for loop requires an unquoted shell variable name after 'for'."
|
|
313
|
+
)
|
|
314
|
+
self.advance()
|
|
315
|
+
items: list[Word] = []
|
|
316
|
+
if self.at_unquoted_word("in"):
|
|
317
|
+
self.advance()
|
|
318
|
+
while True:
|
|
319
|
+
tok = self.peek()
|
|
320
|
+
if tok is None or tok.kind == "OP":
|
|
321
|
+
break
|
|
322
|
+
if tok.kind != "WORD":
|
|
323
|
+
raise UnsupportedConstruct(
|
|
324
|
+
"malformed-syntax", "Unexpected token in a for loop word list."
|
|
325
|
+
)
|
|
326
|
+
items.append(Word(segments=tok.segments or []))
|
|
327
|
+
self.advance()
|
|
328
|
+
elif not (self.at_op(";") or self.at_op("\n")):
|
|
329
|
+
raise UnsupportedConstruct(
|
|
330
|
+
"malformed-syntax", "A for loop requires 'in values' or ';' after its variable name."
|
|
331
|
+
)
|
|
332
|
+
body = self._parse_for_body()
|
|
333
|
+
return ForCommand(name=variable_name, items=items, body=body, redirects=self._parse_redirects())
|
|
334
|
+
|
|
207
335
|
def _parse_redirects(self) -> list[Redirect]:
|
|
208
336
|
redirects: list[Redirect] = []
|
|
209
337
|
while True:
|
|
@@ -20,7 +20,7 @@ _IDENT_START_RE = re.compile(r"[A-Za-z_]")
|
|
|
20
20
|
_IDENT_RE = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
|
21
21
|
@dataclass
|
|
22
22
|
class Token:
|
|
23
|
-
kind: str # "WORD" | "OP"
|
|
23
|
+
kind: str # "WORD" | "OP" | "ARITH"
|
|
24
24
|
text: str | None = None # OP: exact operator text (e.g. ">>", "2>&1", "\n")
|
|
25
25
|
segments: list[Segment] | None = None # WORD: ordered segments
|
|
26
26
|
|
|
@@ -402,6 +402,26 @@ def _scan_operator(src: str, pos: int) -> tuple[str, int]:
|
|
|
402
402
|
raise AssertionError(f"unreachable operator dispatch at {pos!r}")
|
|
403
403
|
|
|
404
404
|
|
|
405
|
+
def _scan_arithmetic_group(src: str, pos: int) -> tuple[str, int]:
|
|
406
|
+
"""Scan ``((...))`` as one token while allowing balanced inner parentheses."""
|
|
407
|
+
depth = 0
|
|
408
|
+
cursor = pos + 2
|
|
409
|
+
while cursor < len(src):
|
|
410
|
+
if src[cursor : cursor + 2] == "))" and depth == 0:
|
|
411
|
+
return src[pos + 2 : cursor], cursor + 2
|
|
412
|
+
char = src[cursor]
|
|
413
|
+
if char == "(":
|
|
414
|
+
depth += 1
|
|
415
|
+
elif char == ")":
|
|
416
|
+
if depth == 0:
|
|
417
|
+
raise UnsupportedConstruct(
|
|
418
|
+
"malformed-syntax", "Arithmetic loop header contains an unmatched ')'."
|
|
419
|
+
)
|
|
420
|
+
depth -= 1
|
|
421
|
+
cursor += 1
|
|
422
|
+
raise _unterminated("arithmetic loop header")
|
|
423
|
+
|
|
424
|
+
|
|
405
425
|
def tokenize(src: str) -> list[Token]:
|
|
406
426
|
n = len(src)
|
|
407
427
|
tokens: list[Token] = []
|
|
@@ -425,7 +445,10 @@ def tokenize(src: str) -> list[Token]:
|
|
|
425
445
|
pos = n if newline == -1 else newline
|
|
426
446
|
continue
|
|
427
447
|
if src[pos : pos + 2] == "((":
|
|
428
|
-
|
|
448
|
+
arithmetic, newpos = _scan_arithmetic_group(src, pos)
|
|
449
|
+
tokens.append(Token(kind="ARITH", text=arithmetic))
|
|
450
|
+
pos = newpos
|
|
451
|
+
continue
|
|
429
452
|
if c.isdigit():
|
|
430
453
|
j = pos
|
|
431
454
|
while j < n and src[j].isdigit():
|
|
@@ -49,11 +49,13 @@ Inspect the scanner, wrapper, package scripts, and CI command before trusting a
|
|
|
49
49
|
For jscpd:
|
|
50
50
|
|
|
51
51
|
1. Prefer an existing repository wrapper such as `npm run check:clones`.
|
|
52
|
-
2.
|
|
53
|
-
3.
|
|
54
|
-
4.
|
|
55
|
-
5.
|
|
56
|
-
6.
|
|
52
|
+
2. Use the Pi-managed jscpd v5 binary; never install jscpd into the project with npm, npx, Bun, or another package manager. If it is unavailable, run `pi doctor` and block or change approach rather than creating project metadata or dependencies.
|
|
53
|
+
3. Write ad-hoc configs and full reports only under Pi-managed work storage or the OS temporary directory. Do not create repository reports, configs, lockfiles, manifests, or `node_modules` without an explicit request.
|
|
54
|
+
4. Verify the pinned major version before relying on its Rust-native CLI. Do not silently fall back to the v4 Node engine.
|
|
55
|
+
5. Explicitly configure `maxLines` and `maxSize`; defaults can omit god units. Keep both far above the largest legitimate owned file.
|
|
56
|
+
6. Pin `minLines`, `minTokens`, `mode`, formats, cross-format policy, exact ignores, JSON reporter, and zero threshold in a regression test.
|
|
57
|
+
7. Reconcile every owned candidate with the analyzed count. Account for below-minimum files by exact path and proof that they cannot contain the configured block size.
|
|
58
|
+
8. Keep console projection bounded and retain the complete JSON report out of model context for explicit inspection.
|
|
57
59
|
|
|
58
60
|
Never make the gate green by raising thresholds, increasing minimum clone size, adding broad ignores, renaming identifiers, or reformatting clone blocks.
|
|
59
61
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: worker-profile-writer
|
|
3
|
+
description: "Use when an orchestrator needs a task-scoped worker profile with a selected model, thinking level, tools, resources, or tighter budget before delegation."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Worker Profile Writer
|
|
7
|
+
|
|
8
|
+
## How to use the skill
|
|
9
|
+
|
|
10
|
+
Use this skill before `delegate` when no owner-authored worker profile is a precise fit.
|
|
11
|
+
|
|
12
|
+
Freedom Dial: high freedom for describing and composing the bounded task; low freedom for authority, persistence, model availability, and budget checks. Pi's core engineering and evidence rules remain mandatory.
|
|
13
|
+
|
|
14
|
+
## North Star
|
|
15
|
+
|
|
16
|
+
Create the smallest immutable profile that can complete one explicit task, then delegate with the exact harness-issued profile ID. The session log owns task profiles; project and user profile directories remain untouched.
|
|
17
|
+
|
|
18
|
+
## Core Sections
|
|
19
|
+
|
|
20
|
+
### 1. Establish the contract
|
|
21
|
+
|
|
22
|
+
1. State the worker's single outcome, required evidence, intended files, and completion boundary.
|
|
23
|
+
2. Call `profile_writer` with `action: "inspect"` when authorized bases or configured model/thinking combinations are unknown.
|
|
24
|
+
3. Choose an owner-authored base whose role and authority already cover the task. Never derive from another generated task profile.
|
|
25
|
+
4. Prefer the fastest model with demonstrated ability to satisfy the task contract. Configuration and authentication prove availability, not fitness; use existing fitness evidence or `model_fitness` when the risk justifies its token cost.
|
|
26
|
+
5. Request only the tools and resource profiles the task needs. Omitted fields inherit the base; supplied fields must be exact subsets. Budgets may only tighten.
|
|
27
|
+
|
|
28
|
+
Human edge: if the task requires an unlisted base, unavailable credentials, broader tools/resources, or a looser budget, stop and ask the user to authorize a durable owner-profile change. Do not route around a rejection.
|
|
29
|
+
|
|
30
|
+
### 2. Execute and verify
|
|
31
|
+
|
|
32
|
+
1. Call `profile_writer` with `action: "create"`; never supply or invent a profile ID.
|
|
33
|
+
2. Confirm `created: true`, the expected base, and the reported changed fields.
|
|
34
|
+
3. Pass the returned `profileId` unchanged to one bounded `delegate` task with explicit acceptance criteria.
|
|
35
|
+
4. Wait for the event-driven terminal handoff, retrieve once with `delegate_status`, and independently verify worker claims.
|
|
36
|
+
5. Treat rejection before persistence as a successful safety boundary. Change the approach or request authority; do not retry the same expansion with different wording.
|
|
37
|
+
|
|
38
|
+
## Anti-Patterns
|
|
39
|
+
|
|
40
|
+
- Writing one-off profile JSON, settings, reports, or dependencies into the repository.
|
|
41
|
+
- Selecting a model or thinking level that `inspect` did not report as available.
|
|
42
|
+
- Giving a worker extra tools "just in case" or loosening the base budget.
|
|
43
|
+
- Inventing, guessing, editing, or reusing a generated profile ID across sessions or branches.
|
|
44
|
+
- Treating a fast model as fit without task-shaped evidence.
|
|
45
|
+
|
|
46
|
+
## Examples
|
|
47
|
+
|
|
48
|
+
Positive: inspect, derive from a read/write implementer, select an authenticated fast model at `low`, narrow tools to `read`, `grep`, and `edit`, tighten token/tool-call limits, then delegate with the returned `task-...` ID.
|
|
49
|
+
|
|
50
|
+
Negative: a base exposes only `read` and `grep`, but the task requires `edit`. Do not request `edit` from `profile_writer`; select an authorized write-capable base or ask the user for an owner-profile change.
|
|
51
|
+
|
|
52
|
+
## Self-Check
|
|
53
|
+
|
|
54
|
+
- The task has one bounded outcome and explicit acceptance evidence.
|
|
55
|
+
- The base is owner-authored and authorized for this orchestrator.
|
|
56
|
+
- Model and thinking level were reported available or otherwise resolved by the harness.
|
|
57
|
+
- Tools/resources are subsets and the budget is unchanged or tighter.
|
|
58
|
+
- The returned ID is passed unchanged; no project/user profile file was created.
|
|
59
|
+
|
|
60
|
+
## Known Gaps
|
|
61
|
+
|
|
62
|
+
- Availability does not prove model quality; use task-shaped fitness evidence where failure risk is material.
|
|
63
|
+
- Generated profiles cannot expand authority or survive outside their owning session branch.
|