induscode 0.1.0__tar.gz
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.
- induscode-0.1.0/.gitignore +21 -0
- induscode-0.1.0/.pindusagi/settings.json +3 -0
- induscode-0.1.0/CHANGELOG.md +72 -0
- induscode-0.1.0/CREDITS.md +22 -0
- induscode-0.1.0/GAP_REPORT.md +150 -0
- induscode-0.1.0/NOTICE +7 -0
- induscode-0.1.0/PARITY_REPORT.md +173 -0
- induscode-0.1.0/PKG-INFO +97 -0
- induscode-0.1.0/README.md +64 -0
- induscode-0.1.0/pyproject.toml +61 -0
- induscode-0.1.0/scripts/lineage_scan.py +91 -0
- induscode-0.1.0/src/induscode/__init__.py +56 -0
- induscode-0.1.0/src/induscode/addons/__init__.py +176 -0
- induscode-0.1.0/src/induscode/addons/contract.py +923 -0
- induscode-0.1.0/src/induscode/addons/dispatch/__init__.py +43 -0
- induscode-0.1.0/src/induscode/addons/dispatch/event_dispatcher.py +348 -0
- induscode-0.1.0/src/induscode/addons/dispatch/tool_interceptor.py +349 -0
- induscode-0.1.0/src/induscode/addons/host.py +469 -0
- induscode-0.1.0/src/induscode/addons/loader.py +314 -0
- induscode-0.1.0/src/induscode/addons/manifest.py +232 -0
- induscode-0.1.0/src/induscode/addons/surface.py +199 -0
- induscode-0.1.0/src/induscode/boot/__init__.py +108 -0
- induscode-0.1.0/src/induscode/boot/auth_vault.py +323 -0
- induscode-0.1.0/src/induscode/boot/boot.py +210 -0
- induscode-0.1.0/src/induscode/boot/contract.py +223 -0
- induscode-0.1.0/src/induscode/boot/invocation.py +117 -0
- induscode-0.1.0/src/induscode/boot/runners/__init__.py +42 -0
- induscode-0.1.0/src/induscode/boot/runners/link_runner.py +82 -0
- induscode-0.1.0/src/induscode/boot/runners/oneshot_runner.py +85 -0
- induscode-0.1.0/src/induscode/boot/runners/registry.py +46 -0
- induscode-0.1.0/src/induscode/boot/runners/repl_runner.py +340 -0
- induscode-0.1.0/src/induscode/boot/runners/session.py +549 -0
- induscode-0.1.0/src/induscode/boot/stages.py +198 -0
- induscode-0.1.0/src/induscode/boot/upgrade/__init__.py +36 -0
- induscode-0.1.0/src/induscode/boot/upgrade/apply.py +125 -0
- induscode-0.1.0/src/induscode/boot/upgrade/upgrades.py +136 -0
- induscode-0.1.0/src/induscode/briefing/__init__.py +115 -0
- induscode-0.1.0/src/induscode/briefing/compose.py +414 -0
- induscode-0.1.0/src/induscode/briefing/contract.py +528 -0
- induscode-0.1.0/src/induscode/briefing/macros.py +721 -0
- induscode-0.1.0/src/induscode/briefing/skills.py +417 -0
- induscode-0.1.0/src/induscode/capability_deck/__init__.py +233 -0
- induscode-0.1.0/src/induscode/capability_deck/bridge_ledger/__init__.py +66 -0
- induscode-0.1.0/src/induscode/capability_deck/bridge_ledger/key.py +181 -0
- induscode-0.1.0/src/induscode/capability_deck/bridge_ledger/ledger.py +276 -0
- induscode-0.1.0/src/induscode/capability_deck/bridge_ledger/network.py +336 -0
- induscode-0.1.0/src/induscode/capability_deck/builtin_bridge.py +358 -0
- induscode-0.1.0/src/induscode/capability_deck/cards/__init__.py +116 -0
- induscode-0.1.0/src/induscode/capability_deck/cards/bg_process.py +482 -0
- induscode-0.1.0/src/induscode/capability_deck/cards/memory.py +226 -0
- induscode-0.1.0/src/induscode/capability_deck/cards/saas.py +280 -0
- induscode-0.1.0/src/induscode/capability_deck/cards/task.py +256 -0
- induscode-0.1.0/src/induscode/capability_deck/cards/todo.py +312 -0
- induscode-0.1.0/src/induscode/capability_deck/contract.py +450 -0
- induscode-0.1.0/src/induscode/capability_deck/manifest.py +126 -0
- induscode-0.1.0/src/induscode/capability_deck/provision.py +217 -0
- induscode-0.1.0/src/induscode/channels/__init__.py +146 -0
- induscode-0.1.0/src/induscode/channels/contract.py +585 -0
- induscode-0.1.0/src/induscode/channels/framer.py +132 -0
- induscode-0.1.0/src/induscode/channels/link/__init__.py +50 -0
- induscode-0.1.0/src/induscode/channels/link/dialog.py +246 -0
- induscode-0.1.0/src/induscode/channels/link/driver.py +308 -0
- induscode-0.1.0/src/induscode/channels/link/server.py +217 -0
- induscode-0.1.0/src/induscode/channels/oneshot.py +178 -0
- induscode-0.1.0/src/induscode/channels/ops.py +140 -0
- induscode-0.1.0/src/induscode/channels/session_ops.py +172 -0
- induscode-0.1.0/src/induscode/conductor/__init__.py +240 -0
- induscode-0.1.0/src/induscode/conductor/catalog.py +309 -0
- induscode-0.1.0/src/induscode/conductor/conductor.py +1084 -0
- induscode-0.1.0/src/induscode/conductor/contract.py +1035 -0
- induscode-0.1.0/src/induscode/conductor/matcher.py +291 -0
- induscode-0.1.0/src/induscode/conductor/serialize.py +575 -0
- induscode-0.1.0/src/induscode/conductor/signal_hub.py +382 -0
- induscode-0.1.0/src/induscode/conductor/skill_parse.py +294 -0
- induscode-0.1.0/src/induscode/conductor/transcript_store.py +449 -0
- induscode-0.1.0/src/induscode/console/__init__.py +236 -0
- induscode-0.1.0/src/induscode/console/app.py +1677 -0
- induscode-0.1.0/src/induscode/console/components/__init__.py +62 -0
- induscode-0.1.0/src/induscode/console/components/banner.py +499 -0
- induscode-0.1.0/src/induscode/console/components/banner_sweep.py +188 -0
- induscode-0.1.0/src/induscode/console/components/emblem.py +181 -0
- induscode-0.1.0/src/induscode/console/components/status_bar.py +102 -0
- induscode-0.1.0/src/induscode/console/contract.py +836 -0
- induscode-0.1.0/src/induscode/console/input/__init__.py +107 -0
- induscode-0.1.0/src/induscode/console/input/chord.py +197 -0
- induscode-0.1.0/src/induscode/console/input/dir_reader.py +113 -0
- induscode-0.1.0/src/induscode/console/input/intents.py +258 -0
- induscode-0.1.0/src/induscode/console/input/providers.py +469 -0
- induscode-0.1.0/src/induscode/console/mount.py +137 -0
- induscode-0.1.0/src/induscode/console/overlays/__init__.py +94 -0
- induscode-0.1.0/src/induscode/console/overlays/auth.py +503 -0
- induscode-0.1.0/src/induscode/console/overlays/pickers.py +526 -0
- induscode-0.1.0/src/induscode/console/overlays/router.py +129 -0
- induscode-0.1.0/src/induscode/console/overlays/sessions.py +232 -0
- induscode-0.1.0/src/induscode/console/reducer.py +145 -0
- induscode-0.1.0/src/induscode/console/resume_picker.py +156 -0
- induscode-0.1.0/src/induscode/console/slash_commands/__init__.py +78 -0
- induscode-0.1.0/src/induscode/console/slash_commands/builtins.py +254 -0
- induscode-0.1.0/src/induscode/console/slash_commands/dynamic.py +217 -0
- induscode-0.1.0/src/induscode/console/slash_commands/integrations.py +949 -0
- induscode-0.1.0/src/induscode/console/slash_commands/transcript.py +404 -0
- induscode-0.1.0/src/induscode/console/slash_commands/workbench.py +430 -0
- induscode-0.1.0/src/induscode/console/startup.py +434 -0
- induscode-0.1.0/src/induscode/console/theme/__init__.py +44 -0
- induscode-0.1.0/src/induscode/console/theme/adapter.py +168 -0
- induscode-0.1.0/src/induscode/console/theme/palette.py +128 -0
- induscode-0.1.0/src/induscode/console/theme/resolve.py +123 -0
- induscode-0.1.0/src/induscode/console/theme/tokens.py +185 -0
- induscode-0.1.0/src/induscode/console_slash/__init__.py +111 -0
- induscode-0.1.0/src/induscode/console_slash/contract.py +185 -0
- induscode-0.1.0/src/induscode/console_slash/registry.py +140 -0
- induscode-0.1.0/src/induscode/console_slash/resolve.py +194 -0
- induscode-0.1.0/src/induscode/console_slash/shared.py +172 -0
- induscode-0.1.0/src/induscode/entry.py +108 -0
- induscode-0.1.0/src/induscode/insight/__init__.py +153 -0
- induscode-0.1.0/src/induscode/insight/collector.py +73 -0
- induscode-0.1.0/src/induscode/insight/replay.py +305 -0
- induscode-0.1.0/src/induscode/insight/wrapper.py +1115 -0
- induscode-0.1.0/src/induscode/kit/__init__.py +82 -0
- induscode-0.1.0/src/induscode/kit/clipboard_image.py +215 -0
- induscode-0.1.0/src/induscode/kit/external_editor.py +120 -0
- induscode-0.1.0/src/induscode/kit/image.py +188 -0
- induscode-0.1.0/src/induscode/kit/shell.py +89 -0
- induscode-0.1.0/src/induscode/kit/tool_fetch.py +288 -0
- induscode-0.1.0/src/induscode/launch/__init__.py +224 -0
- induscode-0.1.0/src/induscode/launch/catalog.py +310 -0
- induscode-0.1.0/src/induscode/launch/contract.py +569 -0
- induscode-0.1.0/src/induscode/launch/credentials.py +852 -0
- induscode-0.1.0/src/induscode/launch/invocation/__init__.py +39 -0
- induscode-0.1.0/src/induscode/launch/invocation/attachments.py +281 -0
- induscode-0.1.0/src/induscode/launch/invocation/flags.py +210 -0
- induscode-0.1.0/src/induscode/launch/invocation/read.py +369 -0
- induscode-0.1.0/src/induscode/launch/invocation/usage.py +110 -0
- induscode-0.1.0/src/induscode/launch/oauth.py +808 -0
- induscode-0.1.0/src/induscode/launch/packages.py +299 -0
- induscode-0.1.0/src/induscode/launch/pickers.py +291 -0
- induscode-0.1.0/src/induscode/py.typed +0 -0
- induscode-0.1.0/src/induscode/runtime_bridge/__init__.py +166 -0
- induscode-0.1.0/src/induscode/runtime_bridge/bridges/__init__.py +66 -0
- induscode-0.1.0/src/induscode/runtime_bridge/bridges/_drive.py +268 -0
- induscode-0.1.0/src/induscode/runtime_bridge/bridges/builtins.py +177 -0
- induscode-0.1.0/src/induscode/runtime_bridge/bridges/claude_cli.py +198 -0
- induscode-0.1.0/src/induscode/runtime_bridge/bridges/codex_cli.py +203 -0
- induscode-0.1.0/src/induscode/runtime_bridge/bridges/indusagi_cli.py +217 -0
- induscode-0.1.0/src/induscode/runtime_bridge/broker.py +397 -0
- induscode-0.1.0/src/induscode/runtime_bridge/contract.py +734 -0
- induscode-0.1.0/src/induscode/runtime_bridge/sink.py +351 -0
- induscode-0.1.0/src/induscode/sessions/__init__.py +25 -0
- induscode-0.1.0/src/induscode/sessions/contract.py +119 -0
- induscode-0.1.0/src/induscode/sessions/library.py +350 -0
- induscode-0.1.0/src/induscode/settings/__init__.py +47 -0
- induscode-0.1.0/src/induscode/settings/contract.py +313 -0
- induscode-0.1.0/src/induscode/settings/manager.py +268 -0
- induscode-0.1.0/src/induscode/transcript_export/__init__.py +109 -0
- induscode-0.1.0/src/induscode/transcript_export/contract.py +522 -0
- induscode-0.1.0/src/induscode/transcript_export/publish.py +455 -0
- induscode-0.1.0/src/induscode/transcript_export/sgr.py +566 -0
- induscode-0.1.0/src/induscode/transcript_export/template.py +319 -0
- induscode-0.1.0/src/induscode/transcript_export/theme_bridge.py +325 -0
- induscode-0.1.0/src/induscode/window_budget/__init__.py +76 -0
- induscode-0.1.0/src/induscode/window_budget/budget/__init__.py +26 -0
- induscode-0.1.0/src/induscode/window_budget/budget/estimate.py +273 -0
- induscode-0.1.0/src/induscode/window_budget/budget/gate.py +60 -0
- induscode-0.1.0/src/induscode/window_budget/budget/slice.py +145 -0
- induscode-0.1.0/src/induscode/window_budget/condenser.py +170 -0
- induscode-0.1.0/src/induscode/window_budget/contract.py +329 -0
- induscode-0.1.0/src/induscode/window_budget/summarize/__init__.py +33 -0
- induscode-0.1.0/src/induscode/window_budget/summarize/condense.py +212 -0
- induscode-0.1.0/src/induscode/window_budget/summarize/prompt.py +241 -0
- induscode-0.1.0/src/induscode/workspace/__init__.py +30 -0
- induscode-0.1.0/src/induscode/workspace/brand.py +96 -0
- induscode-0.1.0/src/induscode/workspace/locator.py +269 -0
- induscode-0.1.0/tests/addons/test_addons.py +589 -0
- induscode-0.1.0/tests/boot/test_boot.py +201 -0
- induscode-0.1.0/tests/boot/test_invocation.py +79 -0
- induscode-0.1.0/tests/boot/test_resume_picker.py +270 -0
- induscode-0.1.0/tests/boot/test_session_persist.py +128 -0
- induscode-0.1.0/tests/briefing/test_briefing.py +267 -0
- induscode-0.1.0/tests/capability_deck/test_cards_provision.py +231 -0
- induscode-0.1.0/tests/capability_deck/test_contract_ledger.py +417 -0
- induscode-0.1.0/tests/channels/test_channels.py +350 -0
- induscode-0.1.0/tests/conductor/test_catalog_store.py +931 -0
- induscode-0.1.0/tests/conductor/test_contract_hub_skill.py +297 -0
- induscode-0.1.0/tests/conductor/test_queue_fork.py +184 -0
- induscode-0.1.0/tests/conductor/test_submit.py +816 -0
- induscode-0.1.0/tests/console/test_banner.py +359 -0
- induscode-0.1.0/tests/console/test_commands_dynamic.py +146 -0
- induscode-0.1.0/tests/console/test_commands_integrations.py +365 -0
- induscode-0.1.0/tests/console/test_commands_transcript.py +283 -0
- induscode-0.1.0/tests/console/test_commands_workbench.py +326 -0
- induscode-0.1.0/tests/console/test_console_app.py +1139 -0
- induscode-0.1.0/tests/console/test_e2e_pilot.py +1010 -0
- induscode-0.1.0/tests/console/test_input.py +660 -0
- induscode-0.1.0/tests/console/test_overlays.py +714 -0
- induscode-0.1.0/tests/console/test_reducer.py +304 -0
- induscode-0.1.0/tests/console/test_slash_handlers.py +328 -0
- induscode-0.1.0/tests/console/test_startup.py +289 -0
- induscode-0.1.0/tests/console/test_theme.py +348 -0
- induscode-0.1.0/tests/console_slash/test_slash_core.py +416 -0
- induscode-0.1.0/tests/insight/test_insight.py +402 -0
- induscode-0.1.0/tests/kit/test_kit.py +162 -0
- induscode-0.1.0/tests/launch/test_launch.py +817 -0
- induscode-0.1.0/tests/launch/test_packages.py +166 -0
- induscode-0.1.0/tests/runtime_bridge/test_runtime_bridge.py +520 -0
- induscode-0.1.0/tests/sessions/test_sessions.py +189 -0
- induscode-0.1.0/tests/settings/test_settings.py +235 -0
- induscode-0.1.0/tests/test_public_api.py +137 -0
- induscode-0.1.0/tests/test_scaffold.py +260 -0
- induscode-0.1.0/tests/transcript_export/test_transcript_export.py +240 -0
- induscode-0.1.0/tests/window_budget/test_window_budget.py +353 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-06-11
|
|
4
|
+
|
|
5
|
+
First release of the Python rebuild. Full port of the TypeScript
|
|
6
|
+
`indusagi-coding-agent` (lineage **v0.1.62**, `indus-code-rebuild`) onto the
|
|
7
|
+
Python `indusagi` framework, built milestone-by-milestone (M0–M6) per
|
|
8
|
+
`indus-code-rebuild/PYTHON_PORT_PLAN/PLAN.md`. **649 tests green**, lineage
|
|
9
|
+
scan clean, wheel verified in a fresh venv.
|
|
10
|
+
|
|
11
|
+
### Subsystems
|
|
12
|
+
|
|
13
|
+
- **workspace** — brand/version (single-sourced via `importlib.metadata`,
|
|
14
|
+
literal fallback) and the `~/.pindusagi` locator composed over the
|
|
15
|
+
framework `Locator` (`INDUSAGI_CODING_AGENT_DIR` > `INDUSAGI_HOME`).
|
|
16
|
+
- **kit** — leaf utilities.
|
|
17
|
+
- **settings** — two-tier `PreferenceStore` (project-write-only).
|
|
18
|
+
- **briefing** — system-prompt composition, macro scanner, SKILL.md walker.
|
|
19
|
+
- **window_budget** — context-window budgeting and condense seam
|
|
20
|
+
(3.6 chars/token heuristic kept).
|
|
21
|
+
- **insight** — tracing wrapper over `indusagi.tracing` with collector sink,
|
|
22
|
+
replay, and FNV-1a gate.
|
|
23
|
+
- **transcript_export** — SGR state machine, theme bridge, HTML publisher on
|
|
24
|
+
`markdown-it-py` + `pygments` (replacing `marked` + `highlight.js`).
|
|
25
|
+
- **conductor** — turn loop with retry/queue-drain/persist-tail, condense
|
|
26
|
+
seam, fork/navigate, signal hub (10-tag translator table), model
|
|
27
|
+
catalog/matcher over `indusagi.ai`, branchable NDJSON `TranscriptStore`,
|
|
28
|
+
message⇄dict codec (`serialize.py`), skill parsing.
|
|
29
|
+
- **runtime_bridge** — snapshot sink (mutable builder, per-push frozen
|
|
30
|
+
snapshots), broker (route/exchange/resume-tap), exchange driver + dialect
|
|
31
|
+
parsers.
|
|
32
|
+
- **capability_deck** — builtin-bridge factory table, `PROFILE_TABLE`
|
|
33
|
+
provisioning, cards (todo ledger, bg-process daemon table on asyncio
|
|
34
|
+
subprocesses with SIGTERM→SIGKILL grace, task/saas/memory), bridge ledger
|
|
35
|
+
with ULID keys (`python-ulid`) and `mount_protocol_bridge` networking.
|
|
36
|
+
- **addons** — discovery walk, importlib loader, event dispatcher
|
|
37
|
+
(gate-throw fails open, transform-throw keeps prior payload), host with
|
|
38
|
+
reserved names and first-wins conflicts.
|
|
39
|
+
- **launch / boot / channels / sessions** — 17-flag invocation table on the
|
|
40
|
+
extended framework parser (`@file` attachments, `--`, list flags, `-pi`
|
|
41
|
+
clustering), credentials + OAuth adapter, multi-account `AuthVault` as the
|
|
42
|
+
single `auth.json` writer (0600, tolerant of framework single-record
|
|
43
|
+
shape), boot orchestrator + runners (oneshot/link/repl), NDJSON channel
|
|
44
|
+
framer (U+2028/29 escapes), session ops + link server + dialog bridge,
|
|
45
|
+
`SessionLibrary` over the transcript store.
|
|
46
|
+
- **console / console_slash** — Textual TUI replacing the Ink console:
|
|
47
|
+
theme engine (4 schemes as native Textual themes), reducer, chrome
|
|
48
|
+
widgets, editor/keybindings with chord latch, autocomplete, overlays as
|
|
49
|
+
`push_screen` flows (incl. OAuth sign-in on `asyncio.Future`), and **26
|
|
50
|
+
slash commands** (18 transcript/workbench built-ins + 8 integration
|
|
51
|
+
commands, plus dynamic skill/template commands).
|
|
52
|
+
- **entry** — console scripts `pindus` and `induscode`.
|
|
53
|
+
|
|
54
|
+
### Key decisions
|
|
55
|
+
|
|
56
|
+
- **Clean-room discipline** — independent implementation; no source copied
|
|
57
|
+
from any prior codebase; enforced by `scripts/lineage_scan.py` and the
|
|
58
|
+
public-API guard test, both release gates.
|
|
59
|
+
- **Framework reuse over reinvention** — LLM abstraction, agent core, MCP
|
|
60
|
+
client, Textual shell primitives, and stage runner come from `indusagi`
|
|
61
|
+
(`[mcp,tui]` extras required); the agent keeps only its own seams
|
|
62
|
+
(transcript tree, vault, signal translation, deck).
|
|
63
|
+
- **Behavioral parity with the TS lineage** — suites ported at full
|
|
64
|
+
strength (649 pytest cases vs the TS line's 332 vitest cases, never
|
|
65
|
+
weakened); versioning restarts at 0.1.0 while the TS line was at 0.1.62.
|
|
66
|
+
- **Library swaps** — `marked`/`highlight.js` → `markdown-it-py`/`pygments`;
|
|
67
|
+
`ulid` → `python-ulid`; Ink/React → Textual; jiti virtual modules →
|
|
68
|
+
plain `importlib` addon loading.
|
|
69
|
+
- **Packaging** — hatchling build; `py.typed` shipped; `NOTICE` and
|
|
70
|
+
`CREDITS.md` embedded via PEP 639 `license-files`; wheel gate = fresh
|
|
71
|
+
venv install (framework wheel first) + `--version`/`--help`/
|
|
72
|
+
`--list-models`/subsystem-import smokes.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Credits
|
|
2
|
+
|
|
3
|
+
The **indusagi coding agent (Python rebuild, `induscode`)** is an independent implementation,
|
|
4
|
+
designed and written from scratch against a behavioral specification and the public API of the
|
|
5
|
+
`indusagi` framework — not by copying source from any prior codebase. It reuses no third-party
|
|
6
|
+
application source code.
|
|
7
|
+
|
|
8
|
+
## indusagi framework
|
|
9
|
+
|
|
10
|
+
The agent runs entirely on the **indusagi** framework (Python rebuild, installed from
|
|
11
|
+
`../indusagi-python-rebuild`), which provides the LLM API abstraction, agent core, Textual-based
|
|
12
|
+
terminal-UI primitives, MCP client, and observability. The framework is itself an independent
|
|
13
|
+
implementation and ships its own `CREDITS.md` / `NOTICE`.
|
|
14
|
+
|
|
15
|
+
## Third-party runtime dependencies
|
|
16
|
+
|
|
17
|
+
Standard open-source libraries used at runtime, each under its own license:
|
|
18
|
+
`markdown-it-py` (transcript-export markdown rendering), `pygments` (transcript-export syntax
|
|
19
|
+
highlighting), `python-ulid` (bridge-ledger keys), plus the framework's `[mcp,tui]` extras
|
|
20
|
+
(`mcp`, `textual`). Dev/test: `pytest`, `pytest-asyncio`, `build`.
|
|
21
|
+
|
|
22
|
+
See `NOTICE` for the notice that must travel with redistributions.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# induscode Python Port — Gap Report
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-06-12
|
|
4
|
+
**Ground truth (TS):** `/Users/varunisrani/indusagi-ts/indus-code-rebuild`
|
|
5
|
+
**Port under audit (Python):** `/Users/varunisrani/indusagi-ts/induscode-python-rebuild`
|
|
6
|
+
|
|
7
|
+
## Executive verdict
|
|
8
|
+
|
|
9
|
+
The Python port is at **near-full functional parity** with the TS ground truth. All 15 subsystems are present (none absent), the entire user-facing surface — 26/26 slash commands, every CLI flag, all console overlays, settings, sessions — matches, and 649 Python tests pass against the same `PYTHON_PORT_PLAN/PLAN.md` baseline the TS rebuild ships. After applying the critic's corrections (one severity downgrade, zero gaps disproven), **7 real gaps remain: 0 high, 1 medium, 6 low** — an estimated **~98% behavioral parity**. The gaps cluster entirely in two subsystems: `launch` (CLI surface) and `insight` (observability/tracing). Nothing in the core interactive agent loop, tool execution, or console UI is broken.
|
|
10
|
+
|
|
11
|
+
**The single most important thing to fix:** the CLI `--resume` flag's interactive session picker. On a real TTY, `pindus --resume` can never present the session list — `_DefaultResumeDeps.mount_picker` raises and the runner silently falls back to a fresh session. It is *medium* (not high) only because the in-console `/resume` slash command is fully wired and reaches the exact same arbitrary-session picker, so the capability is not lost — just degraded at one entry point.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Gap summary table
|
|
16
|
+
|
|
17
|
+
Sorted severity high → low. The critic disproved no gaps; the only correction was downgrading gap #1 from high to medium (capability is reachable via `/resume`).
|
|
18
|
+
|
|
19
|
+
| Feature | Subsystem | Status | Severity |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| CLI `--resume` interactive session picker falls back to fresh | launch | broken | **medium** |
|
|
22
|
+
| GitHub Copilot browser sign-in provider | launch | missing | low |
|
|
23
|
+
| Insight secret redaction over-redacts (whole value vs matched run) | insight | divergent | low |
|
|
24
|
+
| Insight `fail()` does not emit an in-flight update signal | insight | divergent | low |
|
|
25
|
+
| Insight persisted/streamed fault loses error name + stack | insight | partial | low |
|
|
26
|
+
| Insight redactor `omitKeys` option (drop keys wholesale) | insight | missing | low |
|
|
27
|
+
| Top-level package namespace re-export of all subsystems | root barrel | divergent | low |
|
|
28
|
+
|
|
29
|
+
> **Note — not a port gap (DO-NOT-FIX):** the `-p`/`--json`/NDJSON first-chunk drop ("pong"→"ong") is an **upstream framework** defect in `indusagi-python-rebuild/.../agent/agent.py` (`_FacadeTranslator._on_delta` emits the first delta only inside the `MessageStartEvent` partial). The app side (`conductor/signal_hub.py`, `message_start → []`) is parity-correct with TS `translate.ts`, and the TUI masks it via the final `message_end`. Filed upstream; excluded from this table.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Per-subsystem gaps
|
|
34
|
+
|
|
35
|
+
### launch (CLI surface)
|
|
36
|
+
|
|
37
|
+
#### 1. CLI `--resume` interactive picker never mounts on a TTY — *medium, broken*
|
|
38
|
+
|
|
39
|
+
**TS evidence:** `indus-code-rebuild/src/launch/pickers.ts:56-99` — `defaultResumeDeps().mountPicker` renders the real React-Ink `StartupSessionPicker`; the repl runner calls `pickResumeTarget(load, load)` with no overrides and the live picker mounts on a TTY.
|
|
40
|
+
|
|
41
|
+
**Python evidence:**
|
|
42
|
+
- `induscode-python-rebuild/src/induscode/launch/pickers.py:95-105` — `_DefaultResumeDeps.mount_picker` unconditionally `raise RuntimeError("The interactive session picker is wired by the console runner.")`.
|
|
43
|
+
- `…/boot/runners/repl_runner.py:223` — `_choose_resume_target` calls `pick_resume_target(load, load)` with **no injected deps**; grep confirms the raising default is the only `mount_picker` anywhere.
|
|
44
|
+
- At runtime the interactive path returns a typed fault and `repl_runner.py:270` prints `"Resume failed ({detail}); starting a fresh session."` (the framework's Textual `StartupSessionPicker` exists but is unused on this path).
|
|
45
|
+
|
|
46
|
+
**User impact:** On a real terminal, `pindus --resume` always silently starts a brand-new session (one-line stderr notice). The user cannot pick an arbitrary prior session from the CLI flag. **Mitigated:** the in-console `/resume` command is live — `console/slash_commands/transcript.py:196` (`ctx.open_modal("sessions")`) → `console/overlays/router.py:96` (`"sessions": run_session_picker`) → `console/overlays/sessions.py` uses `app.push_screen_wait(...)` on a real Textual session dialog. So a user can run `pindus --resume`, land in a fresh session, type `/resume`, and get the exact arbitrary-session picker. Capability degraded at one entry point, not lost.
|
|
47
|
+
|
|
48
|
+
**Fix hint:** In `repl_runner._choose_resume_target`, inject a Textual-backed `ResumeDeps` whose `mount_picker` reuses the already-wired `run_session_picker` (push it as a modal screen, resolve from its select/close), then pass that as the deps arg to `pick_resume_target` instead of relying on the raising default. The fix is small — the picker plumbing already exists for `/resume`.
|
|
49
|
+
|
|
50
|
+
#### 2. GitHub Copilot browser sign-in provider missing — *low, missing*
|
|
51
|
+
|
|
52
|
+
**TS evidence:** `indus-code-rebuild/src/launch/oauth.ts:47-50` — `BUILT_IN_OAUTH` registers three framework providers: `anthropicOAuthProvider`, `openaiCodexOAuthProvider`, `githubCopilotOAuthProvider`.
|
|
53
|
+
|
|
54
|
+
**Python evidence:** `…/launch/oauth.py:197-213` — `register_built_in_oauth_providers` walks the framework `OAUTH_PROVIDERS` table, which contains only `['anthropic', 'openai']`; `list_login_providers()` emits 2 OAuth rows. The framework `indusagi.llmgateway.credentials.oauth.OAUTH_PROVIDERS` has no `github-copilot` config (Copilot appears only in model-catalog data). The app-side label is already present: `oauth.py:169` `"github-copilot": "GitHub Copilot"`.
|
|
55
|
+
|
|
56
|
+
**User impact:** `pindus signin github-copilot` resolves to an unknown provider; the merged sign-in directory shows 2 of TS's 3 rows. No browser sign-in for Copilot.
|
|
57
|
+
|
|
58
|
+
**Fix hint:** Add a `github-copilot` `OAuthConfig` to the framework `indusagi.llmgateway.credentials.oauth.OAUTH_PROVIDERS` table. Because `_OAUTH_LABELS` already carries the label, `register_built_in_oauth_providers` picks it up automatically once the config exists. This is a framework-data gap, not app logic.
|
|
59
|
+
|
|
60
|
+
### insight (observability / tracing)
|
|
61
|
+
|
|
62
|
+
> Context: per waiver **W4**, the bulk tracing engine stays in the Python framework (`indusagi.tracing`); only the collector sink, replay reader, and the redaction/recorder wrapper are app code. All four insight gaps below are wrapper-side costs of delegating to the framework `SecretScrubber` / `SegmentError` / transport, and are documented inline in `insight/wrapper.py`. None affects the final close record's status or any end-user agent behavior; impact is limited to live trace dashboards and offline trace analysis.
|
|
63
|
+
|
|
64
|
+
#### 3. Secret redaction over-redacts: whole value instead of matched run — *low, divergent*
|
|
65
|
+
|
|
66
|
+
**TS evidence:** `indus-code-rebuild/src/insight/redaction.ts:124-137` — `applyValueRules` does `out.replace(pattern, REDACTED_TOKEN)`, replacing only the matched substring; `insight.test.ts:270-272` asserts `scrubValue("Authorization is Bearer abcdef…")` still contains the `"Authorization is "` prefix.
|
|
67
|
+
|
|
68
|
+
**Python evidence:** `…/insight/wrapper.py:481-484` — `scrub_value` delegates to the framework `SecretScrubber`, which replaces the entire value. At runtime `scrub_value("Authorization is Bearer abcdef…")` returns `"[insight:scrubbed]"` (whole string).
|
|
69
|
+
|
|
70
|
+
**User impact:** Trace attributes that embed a secret inside prose lose all surrounding non-secret context in persisted/streamed traces. Over-redacts (safe) but degrades trace readability vs TS.
|
|
71
|
+
|
|
72
|
+
**Fix hint:** Re-port the TS `applyValueRules` (per-rule `re.sub`, matched-run only) wrapper-side in `SecretRedactor.scrub_value` instead of delegating wholesale, or extend the framework `SecretScrubber` with a matched-run mode. (Documented as W4 cost at `wrapper.py:56-58`.)
|
|
73
|
+
|
|
74
|
+
#### 4. `fail()` emits no in-flight update signal — *low, divergent*
|
|
75
|
+
|
|
76
|
+
**TS evidence:** `indus-code-rebuild/src/insight/recorder.ts:216-224` — `fail()` sets `status='error'` + fault, then `route({phase:'update', …})` immediately, so a live sink observes the failure before close.
|
|
77
|
+
|
|
78
|
+
**Python evidence:** `…/insight/wrapper.py:669-674` — `fail()` only stashes `self._fault` and emits nothing (the framework transport has no probe-carrying update signal). At runtime a failing child probe produces no `update` between its `open` and `close`.
|
|
79
|
+
|
|
80
|
+
**User impact:** A live `signals()`/stream-sink observer (e.g. a real-time trace dashboard) sees a probe's failure only when it closes, not the moment `fail()` is called. No effect on the final close record.
|
|
81
|
+
|
|
82
|
+
**Fix hint:** Have `_LiveProbeHandle.fail()` emit a synthetic `update` through the channel (reconstructing the error-status probe), matching `recorder.ts`. (Documented as W4 cost at `wrapper.py:50-54`.)
|
|
83
|
+
|
|
84
|
+
#### 5. Persisted/streamed fault loses error name + stack — *low, partial*
|
|
85
|
+
|
|
86
|
+
**TS evidence:** `indus-code-rebuild/src/insight/recorder.ts:314-326` — `faultOf` captures `message + name + stack`; `serialize.ts:64-72` persists the full probe incl. `fault.name/stack`; `redaction.ts:223-234` `scrubFault` preserves name and scrubs+caps stack.
|
|
87
|
+
|
|
88
|
+
**Python evidence:** `…/insight/wrapper.py:208-219` — `fault_of` captures name+stack on the in-memory handle, but `close()` (`wrapper.py:683-687`) builds the framework `SegmentError(message=…)` only; `replay.py:174-177` `record_to_probe` rebuilds `fault=ProbeFault(message=…)` with `name=None, stack=None`. At runtime the CloseSignal fault is `ProbeFault(message='boom', name=None, stack=None)`.
|
|
89
|
+
|
|
90
|
+
**User impact:** Persisted NDJSON traces (and the in-process close signal) lose the error type name and stack trace. Offline trace analysis in Python sees only the message; TS retains exception class + stack.
|
|
91
|
+
|
|
92
|
+
**Fix hint:** Either widen the framework `SegmentError`/replay schema to carry `name`/`stack`, or stash them in the close-record attributes wrapper-side and recover them in `record_to_probe`. (Documented as W4 cost at `wrapper.py:50-54` / `replay.py`.)
|
|
93
|
+
|
|
94
|
+
#### 6. Redactor `omitKeys` option missing — *low, missing*
|
|
95
|
+
|
|
96
|
+
**TS evidence:** `indus-code-rebuild/src/insight/contract.ts:470-474` defines `RedactionOptions.omitKeys`; `redaction.ts:155-220` `createRedactor` honors it — matching keys are deleted from the scrubbed output entirely (not tokenized).
|
|
97
|
+
|
|
98
|
+
**Python evidence:** `…/insight/wrapper.py:538-544` — `create_redactor` takes only `patterns` + `max_string_length`; the framework `SecretScrubber.__init__` signature is `(patterns, token)` with no omit support (confirmed via `inspect`); `SecretRedactor` has no omit path.
|
|
99
|
+
|
|
100
|
+
**User impact:** Callers cannot configure keys to be dropped from trace output (only tokenized). In practice TS never wires actual `omitKeys` in production code, so the default redactor behaves identically — pure API-surface gap.
|
|
101
|
+
|
|
102
|
+
**Fix hint:** Add an `omit_keys` parameter to `create_redactor`/`SecretRedactor` and delete matching keys before/after the `SecretScrubber` pass (the scrubber walks but never removes keys). Low priority — unused in TS production paths.
|
|
103
|
+
|
|
104
|
+
### root barrel (public library surface)
|
|
105
|
+
|
|
106
|
+
#### 7. Top-level namespace re-export of all subsystems missing — *low, divergent*
|
|
107
|
+
|
|
108
|
+
**TS evidence:** `indus-code-rebuild/src/index.ts:9-25` re-exports all 15 subsystems as namespaces (`export * as runtimeBridge from "./runtime-bridge";`, and likewise for every other subsystem).
|
|
109
|
+
|
|
110
|
+
**Python evidence:** `induscode-python-rebuild/src/induscode/__init__.py:8-18` re-exports only `workspace` (+ `VERSION`/`__version__`); `__all__` lists just those. The barrel docstring still references the M0-era stub state.
|
|
111
|
+
|
|
112
|
+
**User impact:** A consumer cannot do `import induscode; induscode.runtime_bridge…` the way TS allows `agent.runtimeBridge…`. The idiomatic Python path `from induscode.runtime_bridge import …` works identically, so there is no behavioral consequence. This is a build-wide barrel convention difference, not specific to any one subsystem.
|
|
113
|
+
|
|
114
|
+
**Fix hint:** If top-level namespace access is desired, add `from induscode import <subsystem>` imports plus the names to `induscode/__init__.py` `__all__`. This is a cross-cutting decision affecting all 15 subsystems; refresh the stale M0 barrel docstring at the same time. Low priority — no functional gap.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Deliberate / accepted differences (waived ports — intentional, not bugs)
|
|
119
|
+
|
|
120
|
+
These 13 differences are plan-cited waivers in PARITY_REPORT. They are by-design ports of TS behavior onto Python/Textual/framework idioms, fully tested, and **not** defects:
|
|
121
|
+
|
|
122
|
+
- **W1 — Composer deleted.** The TS bespoke composer is replaced by the framework `PromptEditor`; folded into `console/app.py` + `console/input/`. Structural, not missing.
|
|
123
|
+
- **W2 — Paste vault / burst-debounce deleted.** Replaced by framework `EditorCore` paste markers.
|
|
124
|
+
- **W3 — jiti → importlib.** `.py` addons load via `spec_from_file_location` (`addons/loader.py`); the TS virtual-module/alias bridge collapses and `BUNDLED_NAMESPACES` is vestigial.
|
|
125
|
+
- **W4 — insight wraps `indusagi.tracing`.** ~2.5k LOC of tracing stays in the framework; only collector sink + replay reader + FNV-1a gate are app code. (Explains the lower Python insight LOC and gaps #3–#6 above.)
|
|
126
|
+
- **W5 — repl placeholder → real Textual mount.** The M0 `cli.py` stub is replaced by a real console mount in `boot/runners/repl_runner.py` (the seam stays injectable).
|
|
127
|
+
- **W6 — runtime-bridge barrel-only.** A tested library layer in **both** builds; deliberately not wired into the boot path in either.
|
|
128
|
+
- **W7 — `/memory` on/off cosmetic.** Flips a reporting flag only; never removes the card. Matches TS.
|
|
129
|
+
- **W8 — two distinct compaction engines.** `window_budget` (tokens, 0.75 threshold, 3.6 chars/token) is the wired conductor `CondenseFn`; the framework `runtime.memory` (turns, 0.8) is intentionally unwired — same as TS.
|
|
130
|
+
- **W9 — chord-latch break semantics.** Timer-bounded under Textual; behavioral parity with the TS chord latch.
|
|
131
|
+
- **W10 — OAuth overlay on `asyncio.Future`.** The OAuth overlay resolves via a Future instead of the TS promise; same UX.
|
|
132
|
+
- **W11 — TypeBox → literal JSON-schema dicts.** Addon/tool schemas are plain dicts instead of TypeBox builders.
|
|
133
|
+
- **W12 — modal routing inversion.** TS always-mounted overlays become `push_screen` via `console/overlays/router.py`'s `OVERLAY_HANDLERS` table.
|
|
134
|
+
- **W13 — todo wire names.** `todo_read` / `todo_set` (vs TS internal names); functionally identical.
|
|
135
|
+
|
|
136
|
+
**Out of scope (TS feature ceiling, not a Python parity baseline):** `FEATURE_GAP_ROADMAP.md`, `UI_IMPROVEMENTS_FROM_INDUSCODE.md`, `UI_UPGRADE_PLAN.md`, `CHANGELOG.md` describe the gap between the TS rebuild and the upstream `induscode` Claude-Code fork (markdown→ANSI rendering, colored diffs, syntax highlight, read-before-edit/mtime gate, deep-recursive grep, broken-`find`-glob fix, LSP diagnostics, plan mode, CLAUDE.md ingestion). Both rebuilds wire the **same** framework `facade/bot/actions` built-ins (verified: Python `capability_deck/builtin_bridge.py` + `manifest.py` wire identical read/write/edit/ls/grep/find built-ins), so these affect TS and Python equally and are **not** Python parity gaps.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Recommended fix order
|
|
141
|
+
|
|
142
|
+
Highest user-impact first.
|
|
143
|
+
|
|
144
|
+
1. **Wire the CLI `--resume` Textual picker** (gap #1, medium). In `boot/runners/repl_runner.py:_choose_resume_target`, inject a `ResumeDeps` whose `mount_picker` reuses the already-live `run_session_picker` (push a modal Textual screen, resolve from select/close) and pass it to `pick_resume_target(...)`. Removes the silent fresh-session fallback on `pindus --resume`. Small fix — the picker already exists for `/resume`.
|
|
145
|
+
2. **Add the GitHub Copilot OAuth config** (gap #2, low). Add a `github-copilot` `OAuthConfig` to the framework `indusagi.llmgateway.credentials.oauth.OAUTH_PROVIDERS`. App label is pre-wired, so registration is automatic. Restores browser sign-in for Copilot.
|
|
146
|
+
3. **Port matched-run secret redaction** (gap #3, low). Re-implement `applyValueRules` (per-rule `re.sub`, matched run only) in `SecretRedactor.scrub_value` instead of delegating wholesale to `SecretScrubber`. Restores trace readability around embedded secrets.
|
|
147
|
+
4. **Persist fault name + stack** (gap #5, low). Stash `name`/`stack` in the close-record attributes wrapper-side and recover them in `replay.record_to_probe` (or widen the framework `SegmentError`/replay schema). Restores offline-debug fidelity.
|
|
148
|
+
5. **Emit in-flight `fail()` update** (gap #4, low). Have `_LiveProbeHandle.fail()` push a synthetic `update` signal so live trace dashboards see failures before close.
|
|
149
|
+
6. **Add `omit_keys` to the redactor** (gap #6, low). Add the parameter to `create_redactor`/`SecretRedactor` and delete matching keys around the scrubber pass. Closes the API-surface gap (unused in TS production).
|
|
150
|
+
7. **Restore the top-level subsystem barrel** (gap #7, low). Re-export all 15 subsystems from `induscode/__init__.py` and refresh the stale M0 docstring. Cosmetic library-surface parity; no behavioral change.
|
induscode-0.1.0/NOTICE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
induscode — indusagi coding agent (Python rebuild)
|
|
2
|
+
Copyright (c) 2026 indusagi
|
|
3
|
+
|
|
4
|
+
This product is an independent implementation, designed and written from scratch.
|
|
5
|
+
It reuses no third-party application source code.
|
|
6
|
+
|
|
7
|
+
This agent runs on the indusagi framework, which ships its own NOTICE.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# PARITY_REPORT.md — induscode Python rebuild vs TS ground truth
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-06-11 · **Milestone:** M6 (parity-audit half)
|
|
4
|
+
**Python build:** `/Users/varunisrani/indusagi-ts/induscode-python-rebuild`
|
|
5
|
+
**TS ground truth:** `/Users/varunisrani/indusagi-ts/indus-code-rebuild`
|
|
6
|
+
**Checklist audited:** `indus-code-rebuild/PYTHON_PORT_PLAN/PLAN.md` §6, row by row, against actual code + tests (not file names).
|
|
7
|
+
|
|
8
|
+
## Summary
|
|
9
|
+
|
|
10
|
+
| Metric | Result |
|
|
11
|
+
|---|---|
|
|
12
|
+
| **Verdict** | **PASS — full feature parity.** 106/106 checklist rows **done**; 0 partial, 0 missing. 13 deliberate, plan-cited waivers/divergences documented below. 2 plan-doc errata (not port gaps). |
|
|
13
|
+
| Test suite | **649 passed**, 0 failed (`.venv/bin/python -m pytest -q`, 8.07 s) |
|
|
14
|
+
| Fresh-venv wheel gate | **PASS** (packaging half of M6, per the packaging agent) |
|
|
15
|
+
| Live smoke (a–d) | 4/4 executed: (b) (c) (d) clean PASS; (a) exit 0 with one **upstream framework defect** — first streamed chunk dropped from `-p` text output (root cause below; not fixed, per instructions) |
|
|
16
|
+
| Rows by area | flags 17 + conventions 6 · slash 26 static + 4 dynamic + 10 aliases · overlays 12 + console-other 4 · channels 5 · addons 4 + deck 4 · sessions 8 · runtime-bridge 2 · aux 4 = **106** |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Live smoke results (owner's key; `INDUSAGI_HOME=/tmp/induscode-live-test`)
|
|
21
|
+
|
|
22
|
+
| # | Command | Expected | Result |
|
|
23
|
+
|---|---|---|---|
|
|
24
|
+
| a | `.venv/bin/pindus -m claude-haiku-4-5 -p "reply with the word pong"` | `pong`, exit 0 | **PASS w/ DEFECT** — exit 0, model resolved (`claude-haiku-4-5` accepted by catalog/matcher; no resolution issue), but stdout was `ong\n`. Reproduced twice; a second probe (`reply with exactly: strawberry banana`) printed `berry banana` — the **entire first streamed chunk** is dropped, not a fixed character count. Root cause below. |
|
|
25
|
+
| b | `echo '{"jsonrpc":"2.0","id":1,"method":"snapshot"}' \| .venv/bin/pindus --json` (12 s harness, kill after reply) | one JSON-RPC reply line | **PASS** — `{"jsonrpc":"2.0","id":1,"result":{"model":…,"sessionId":"s_…","usage":{…}}}` — a flat `LinkSnapshot`; process exited 0 on stdin close. |
|
|
26
|
+
| c | TUI under a pty (`pty.fork`, 120×40, wait ~5 s, prompt, wait ~8 s, `/quit\r`) | banner wordmark, streamed text, clean exit | **PASS** — `INDUS CODE` block-art wordmark + `indus console v0.1.0` rendered; session card showed `model claude-haiku-4-5`; streamed reply arrived (transient `ong` during streaming, **final render `pong`** — the defect is masked in the TUI because `message_end` carries the full message); `/quit` exited with status 0. Capture: 57,009 bytes. |
|
|
27
|
+
| d | `pindus signin --list` (sandboxed) | exit 0 | **PASS** — printed `Saved accounts:\n (none)`, exit 0. |
|
|
28
|
+
|
|
29
|
+
Side evidence from the live run: sessions were written to `/tmp/induscode-live-test/sessions/--Users-varunisrani-indusagi-ts-induscode-python-rebuild--/s_*.ndjson` with first line `{"schema":"indus/transcript@1",…}` — live confirmation of per-cwd `--<slug>--` scoping, the transcript header, and `INDUSAGI_HOME` honoring.
|
|
30
|
+
|
|
31
|
+
### Root cause: dropped first streamed chunk (smoke a) — DO-NOT-FIX filed
|
|
32
|
+
|
|
33
|
+
- **Where:** framework, `/Users/varunisrani/indusagi-ts/indusagi-python-rebuild/src/indusagi/agent/agent.py`, `_FacadeTranslator._on_delta` (line 915).
|
|
34
|
+
- **Mechanism:** on the **first** text delta of an assistant message, `_message_open` is false, so the translator emits only `MessageStartEvent(message=partial)` (line 924) — the first chunk's text travels **only inside the partial message**. `MessageUpdateEvent(assistantMessageEvent={"type":"text_delta","delta":…})` is emitted for second-and-later deltas only (lines 926–931, the `else` branch).
|
|
35
|
+
- **App side (parity-correct):** induscode's translator maps `message_start → []` (`/Users/varunisrani/indusagi-ts/induscode-python-rebuild/src/induscode/conductor/signal_hub.py` line 355 — same as TS `translate.ts`), so no `TextSignal` is ever produced for the first chunk. The oneshot text strategy (`channels/oneshot.py`, `_text_strategy`) accumulates only `TextSignal` deltas → `-p` output is missing the first chunk ("pong" → "ong").
|
|
36
|
+
- **Blast radius:** `-p` text output (truncated), `-p` NDJSON signal stream and `--json` link signal stream (first text chunk absent from the `text` signal sequence), TUI in-flight stream (transient; final render is correct because `message_end` carries the complete assistant message).
|
|
37
|
+
- **Proposed fix (not applied):** in `_FacadeTranslator._on_delta`, when opening the message emit `MessageStartEvent` with an **empty** partial and then *always* emit the `MessageUpdateEvent` carrying the delta (i.e., move the update emit out of the `else`). That matches the TS framework's framing, where the first delta arrives as a `message_update`. An app-side workaround (extracting trailing text from the `message_start` partial in `signal_hub.py`) would diverge from the TS translator table and is not recommended.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## `--tools` `_`/`-`-insensitivity: settled verdict
|
|
42
|
+
|
|
43
|
+
**DONE — full parity; the plan row's wording is misattributed, not wrong.** Evidence:
|
|
44
|
+
|
|
45
|
+
- **TS:** `src/launch/invocation/read.ts` `resolveTools` (line 252) filters the parsed list to **exact** roster names via `isToolName` — no normalization at parse time. The normalization lives at the deck-filter seam: `src/boot/runners/session.ts` line 261, `const canon = (name) => name.toLowerCase().replace(/[_-]/g, "")` inside `selectTools` — it exists so a `--tools web_fetch,todo_read` request lines up with the deck's `webfetch`/`todoread` ids (comment at lines 252–254).
|
|
46
|
+
- **Python:** identical at both seams. Parse-time exact filter: `launch/invocation/read.py` `_resolve_tools` (line 265) + `launch/contract.py` `is_tool_name` (line 198) over the identical 13-name `TOOL_NAMES` roster (contract.py:181 ↔ contract.ts:147). Deck-filter canon: `boot/runners/session.py` `_canon_tool_name` (line 314, `re.sub(r"[_-]", "", name.lower())`) used in `select_tools` (lines 320–330) — including the same quirk that an allow-list emptied by parse-time filtering falls back to **all** tools (session.py:327 ↔ session.ts:260).
|
|
47
|
+
- **Plan-doc erratum:** PLAN.md §6 attributes the insensitivity to `launch/invocation/flags.py`; in **both** codebases it lives in the boot session runner's deck filter. The CLI auditor's PARTIAL flag is overturned.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Audit tables (condensed; full evidence cited per row)
|
|
52
|
+
|
|
53
|
+
### 1. CLI flags (17) + conventions (6) — 23/23 done
|
|
54
|
+
|
|
55
|
+
| Row | Status | Evidence (Python · test) |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `--print`/`-p` oneshot | done | `launch/invocation/flags.py:96` ; `_derive_mode` `read.py:243-253` · `tests/launch/test_launch.py:187-218` |
|
|
58
|
+
| `--json`/`--rpc` wins over `-p` | done | `flags.py:103`; json checked before print in `_derive_mode` · `test_launch.py:220-223`, `tests/boot/test_invocation.py:71-75`; live smoke (b) |
|
|
59
|
+
| `--interactive`/`-i` force REPL | done | `flags.py:110` · `test_launch.py:226-229` |
|
|
60
|
+
| `--model`/`-m` | done | `flags.py:118` · `tests/boot/test_boot.py:62-66`; live smoke (a/c) |
|
|
61
|
+
| `--account` | done | `flags.py:125` · `test_boot.py:20-25` |
|
|
62
|
+
| `--thinking` 6-level | done | `flags.py:131` + `THINKING_EFFORTS` (off/minimal/low/medium/high/xhigh) `contract.py:139-146` · `test_boot.py:20-25` |
|
|
63
|
+
| `--list-models [substr]` | done | `flags.py:137`; filter projection `boot/invocation.py` · `test_invocation.py:49-57` |
|
|
64
|
+
| `--cwd` | done | `flags.py:144` · `test_boot.py:83-87` |
|
|
65
|
+
| `--resume`/`-r` picker | done | `flags.py:162`; `boot/invocation.py:87` · `test_invocation.py:42-47` |
|
|
66
|
+
| `--continue`/`-c` newest-in-cwd | done | `flags.py:169`; `boot/invocation.py:88` · `test_invocation.py:42-47` |
|
|
67
|
+
| `--tools` (13-name roster, `_`/`-`-insensitive) | **done** | settled above: `contract.py:181-200`, `read.py:265-271`, `boot/runners/session.py:314-330` · `test_boot.py:33-36` |
|
|
68
|
+
| `--no-tools` | done | `flags.py:183`; `read.py:345` · `test_boot.py:33-36`, `test_launch.py:200-201` |
|
|
69
|
+
| `--mcp` config-path list | done | `flags.py:189`; accumulate + comma-split `read.py:126-175` · `test_launch.py:193-196`, `test_invocation.py:38-40` |
|
|
70
|
+
| `--help`/`-h` from the table | done | `flags.py:196`; `render_usage` walks `FLAG_SPECS` `usage.py:72-110` · `test_boot.py:99-105`, `test_launch.py:237-248` (FLAG_SPECS↔usage coverage) |
|
|
71
|
+
| `--version`/`-v` → `induscode 0.1.0` | done | `flags.py:203`; `boot.py:91-94` `f"{BRAND.name} {VERSION}"`; `workspace/brand.py:66-96` · `test_boot.py:107-112` |
|
|
72
|
+
| `--system` file-or-literal | done | `flags.py:150`; `resolve_text` in session runner · `test_boot.py:27-31` |
|
|
73
|
+
| `--append-system` file-or-literal | done | `flags.py:156` (hyphenated bag key) · `test_boot.py:27-31` |
|
|
74
|
+
| `@file` attachments (text/image, caps, typed errors) | done | `invocation/attachments.py:42-150` (10 MB text / 20 MB image; not-found/too-large/unsupported/read-failed) · `test_launch.py:256-283` |
|
|
75
|
+
| `--` terminator | done | `read.py:300-303` + `read_file_references` re-walk · parser tests |
|
|
76
|
+
| `-pi` clustering | done | `_is_short_cluster`/`_apply_short_cluster` `read.py:71-74, 210-222` (booleans only) · integration parses |
|
|
77
|
+
| unknown-flag tolerance → flag bag | done | `read.py:327-330` extension-flag hatch |
|
|
78
|
+
| `signin\|login`/`signout\|logout` + `--provider/--account/--method/--oauth/--api-key/--list` | done | `launch/credentials.py:343-366`; OAuth adapter `launch/oauth.py` · `test_launch.py:343-381`; live smoke (d) |
|
|
79
|
+
| `install/remove/update/list/config` packages | done | `launch/packages.py:49-57` over framework parser engine · `tests/launch/test_packages.py` |
|
|
80
|
+
|
|
81
|
+
### 2. Slash commands — 26 static + dynamic + aliases, all done
|
|
82
|
+
|
|
83
|
+
| Group | Status | Evidence |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| Transcript ×11 (`/clear /new /summarize-context /resume /session /branch /timeline /name /reload /quit /exit`) | done | `console/slash_commands/transcript.py:343-403` (handlers L133-331; tracked fire-and-forget `_spawn` L87-91) · `tests/console/test_commands_transcript.py` (14 cases) |
|
|
86
|
+
| Workbench ×7 (`/model /models-for /settings /help /keys /whats-new /debug`) | done | `workbench.py:387-430` (21-row HOTKEYS table; late-bound help registry; /debug JSONL via message⇄dict codec) · `test_commands_workbench.py` (13 cases) |
|
|
87
|
+
| Integrations ×8 (`/login /logout /mcp /memory /composio /copy /export /share`) | done | `integrations.py:901-949`; per-console `IntegrationsRuntime` holder L102-138; `/share` runs `gh gist create` and degrades to local HTML path on missing/unauth gh · `test_commands_integrations.py` (18 cases) |
|
|
88
|
+
| Dynamic `/skill:<name>` + `/<template>` macros + collision filtering | done | `dynamic.py:129-176` (skill XML block w/ attr escaping; `$1/$2/$ARGUMENTS` + `{{arg.*}}` macro expansion via briefing/macros); self-collision dedup `dynamic.py:202-209`; reserved-token filter `builtins.py:196-206` · `test_commands_dynamic.py` |
|
|
89
|
+
| Aliases ×10 (`reset · condense/compact · sessions · fork · tree · models · scoped-models · ? · hotkeys · changelog`) | done | alias tuples on each `SlashCommand` (transcript.py:346,357,364,375,381; workbench.py:392,398,411,417,423) · alias-coverage test in each group |
|
|
90
|
+
|
|
91
|
+
### 3. Overlays, theme, input, startup — 16/16 done
|
|
92
|
+
|
|
93
|
+
| Row | Status | Evidence |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| 12 ModalKinds: models · scopedModels (edit/show/reset) · settings · theme · sessions · tree · userTurns · signIn (provider threading) · signOut · oauth (full flow on `asyncio.Future`) · plugin (mcp/memory/composio) · confirm-class | done | `console/overlays/router.py` `OVERLAY_HANDLERS` (12 entries → awaited `push_screen` flows); `overlays/pickers.py`, `sessions.py`, `auth.py` · `tests/console/test_overlays.py::TestRouterTable::test_covers_every_modal_kind` (`len(OVERLAY_HANDLERS)==12` vs `MODAL_KINDS`) |
|
|
96
|
+
| Settings overlay rows | done | Python 15 rows == TS `pickers.tsx:418-542` (15). **Plan-doc erratum:** §6 says "16 rows"; TS ground truth has 15 — port matches TS, not the plan. |
|
|
97
|
+
| 4 theme schemes w/ live preview | done | `console/theme/resolve.py` THEMES: midnight, daylight, midnight-cb, daylight-cb; preview-on-highlight w/ Esc-revert / Enter-commit-persist (`pickers.py::run_theme_picker`) · `tests/console/test_theme.py` (9 classes) |
|
|
98
|
+
| 21-chord `/keys` + chord latch + double-Ctrl+C | done | HOTKEYS (21) `workbench.py`; `input/intents.py` INTENT_TABLE; `input/chord.py` `advance_chord` (600 ms) + `advance_exit_window` (500 ms) · `tests/console/test_input.py` (TestIntentTable 15 cases, TestAdvanceChord, TestExitWindow); live smoke (c) clean `/quit` |
|
|
99
|
+
| Startup map + changelog splash keyed to `lastSeenVersion` | done | `console/startup.py` `gather_startup`/`gather_changelog` (full/condensed/none modes) · `tests/console/test_startup.py` |
|
|
100
|
+
| Clipboard image paste + external `$EDITOR` | done | `kit/clipboard_image.py`, `kit/external_editor.py`, wired at `console/app.py:1225-1246` (`input:pasteImage`, `input:externalEditor`); paste markers via framework `EditorCore` · `test_input.py::TestPasteMarkers` |
|
|
101
|
+
|
|
102
|
+
### 4. Channels — 5/5 done
|
|
103
|
+
|
|
104
|
+
| Row | Status | Evidence |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `SESSION_OPS` ×6 (`submit abort snapshot resume listModels cycleModel`) w/ flat `LinkSnapshot` | done | `channels/session_ops.py:163-172` registry; `project_snapshot` L63-104 (TS wire vocabulary verbatim); optional `cycleModel` probed à la TS `cycleModel?.()` · `tests/channels/test_channels.py:320-349`; live smoke (b) |
|
|
107
|
+
| Dialog bridge `ask`/`tell` + 7 methods, 90 s timeout→fallback | done | `channels/link/dialog.py:187-216` frozen `DIALOG_TABLE` (select/confirm/input/editor=ask; notify/status/title=tell); `loop.call_later(dialogMs/1000)`; `dialogMs=90_000` `contract.py:544` ↔ TS `contract.ts:542-546` · linked-server round-trips `test_channels.py:297-316` |
|
|
108
|
+
| Oneshot text + ndjson (start/signal/end frames, exit by phase) | done | `channels/oneshot.py:75-131`; exit 0/1 by `phase=="faulted"` L68-72 · `test_channels.py:240-289` (3 strategy tests); live smoke (a) exercised text path |
|
|
109
|
+
| Framer U+2028/29 + split-rune reassembly | done | `channels/framer.py:64-89` explicit `
`/`
` escape (load-bearing — `json.dumps(ensure_ascii=False)` does **not** escape them); `codecs.getincrementaldecoder("utf-8")` fresh per call L92-122 · `test_channels.py:185-221` incl. mid-rune emoji split pin |
|
|
110
|
+
| JSON-RPC codes exact + concurrent requests | done | `channels/contract.py:163-176` `OP_ERROR` = −32700/−32600/−32601/−32602/−32000; `link/server.py:191-217` per-request `asyncio.create_task` + inflight set + `gather` at stream end (deadlock-free asks) · `test_channels.py:340-349` |
|
|
111
|
+
|
|
112
|
+
### 5. Addons + capability deck — 8/8 done
|
|
113
|
+
|
|
114
|
+
| Row | Status | Evidence |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| 13 HookEvents × observe/transform/gate; gates only `tool:before`/`input:submit`/`compact:before` | done | `addons/contract.py:282-296` (13-tag union == TS `contract.ts:219-232`); EVENT_TRAITS ×13 dispatch table; RESERVED_EVENTS = the 3 gate-bearers · `tests/addons/test_addons.py:477-484` key-coverage test |
|
|
117
|
+
| Interceptor onion (enter-forward, exit-reverse, error-recovery) | done | `AddonInterceptorChain` (tool interceptor): enter in order w/ block short-circuit, exit reversed, stage-throw → `AddonFault` no-op, exit may recover tool error · `test_addons.py:269-305` |
|
|
118
|
+
| Addon commands/tools w/ reserved-name + duplicate conflict faults | done | `addons/host.py:447-460`; first-wins; `AddonFault(kind="conflict")` · `test_addons.py:399-429` |
|
|
119
|
+
| `.indus/addons` `*.py` discovery, importlib loader, fault isolation | done | `addons/manifest.py` discovery walk (bare `*.py` + `__init__.py` dirs, dot-skip, sorted, never throws); `addons/loader.py` `_ImportlibLoader` via `spec_from_file_location` (fresh synthetic module name per load); load error → `AddonFault(kind="load")`, agent never crashes · real-importlib end-to-end `test_addons.py:492-540` |
|
|
120
|
+
| Profiles `authoring`/`survey`/`all` w/ exact inverted membership | done | `capability_deck/manifest.py:109-126` PROFILE_TABLE verbatim incl. the TS inversion quirk (preserved per PLAN.md:164/259) · `tests/capability_deck/test_cards_provision.py` |
|
|
121
|
+
| 12 framework built-ins | done | `builtin_bridge.py:307-310` `builtin_descriptors()`: read, ls, grep, find, websearch, webfetch, todo_read, write, edit, bash, process, todo_set (== TS `builtin-bridge.ts:124-233`; todo wire names follow the Python framework's `todo_read`/`todo_set`) |
|
|
122
|
+
| 5 app cards (todo · bg-process real spawn/poll/stop · task · saas-action · memory) | done | `cards/__init__.py:110-116` `APP_NOVEL_CARDS`; DaemonTable on `asyncio.create_subprocess_shell`, 2000-line ring buffers, SIGTERM→SIGKILL 3 s grace · `test_cards_provision.py` spawns a **real** subprocess, polls, stops |
|
|
123
|
+
| Bridge ledger (content/ULID keys, enroll/retire/withdraw, mount/detach `DeckFault("bridge")`) | done | `bridge_ledger` ledger (immutable log + pure `reduce_ledger` fold, upsert-by-key); sha256 content key / ULID; `DeckFault("bridge")` returned, never thrown · `tests/capability_deck/test_contract_ledger.py` |
|
|
124
|
+
|
|
125
|
+
### 6. Sessions, transcripts, export, runtime-bridge, aux — 14/14 done
|
|
126
|
+
|
|
127
|
+
| Row | Status | Evidence |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| Branchable NDJSON tree `indus/transcript@1` | done | `conductor/contract.py` `TRANSCRIPT_SCHEMA`; `transcript_store.py` parent-linked tree; `serialize.py` envelope · `tests/conductor/test_catalog_store.py`; live artifact header confirmed |
|
|
130
|
+
| Resume by id + deepest-leaf recovery | done | `conductor.py` `resume()`; `_deepest_leaf` (ULID tie-break) · `test_catalog_store.py::test_deepest_leaf_*` |
|
|
131
|
+
| Branch/fork from prior turn | done | `transcript_store.py` `branch_at()` (repoint leaf, no rewrite) · `tests/conductor/test_queue_fork.py` |
|
|
132
|
+
| Tree navigation | done | `sessions/library.py` `tree()` + `prior_turns()` projections · `tests/sessions/test_sessions.py` |
|
|
133
|
+
| Per-cwd `--<slug>--` + `--continue` | done | `boot/runners/session.py` `session_scope_dir` · `tests/boot/test_session_persist.py`; live artifact path confirmed |
|
|
134
|
+
| Legacy framework-session import | done | `serialize.py` `import_legacy_file/_text`, `resolve_legacy_messages` → framework loaders (`load_entries_from_file`, `parse_session_entries`, …) · conductor legacy tests |
|
|
135
|
+
| `/export` self-contained HTML (base64 payload, SGR-painted tool output, widget splice) | done | `transcript_export/publish.py` (base64 `data-turns`), `sgr.py` state machine (bold/red/256/24-bit), widget splice by toolCallId in `template.py` · `tests/transcript_export/test_transcript_export.py` |
|
|
136
|
+
| `/share` gh-gist degradations | done | `integrations.py` `_run_share`: `gh gist create`, `FileNotFoundError`/auth-failure → local-path fallback message · `test_commands_integrations.py` |
|
|
137
|
+
| Runtime-bridge 3 dialects (claude-cli, codex-cli, indusagi-cli) | done | `runtime_bridge/bridges/{claude_cli,codex_cli,indusagi_cli}.py` → provider-neutral `NormalizedEvent`; `sink.py` → framework `AssistantMessageEventStream` · `tests/runtime_bridge/test_runtime_bridge.py` (17 cases: sink 3, dialects 2+2+2, broker 8, credentials 4) |
|
|
138
|
+
| Runtime-bridge wiring stance | done (parity note) | Library-only in **both** builds: "runtime-bridge is currently consumed only via the package barrel (`src/index.ts: export * as runtimeBridge`) — it is a complete, fully-tested library layer not yet wired into the boot path" (analysis 01 §1, line 12); Python mirrors via `runtime_bridge/__init__.py:14-16` |
|
|
139
|
+
| Auth vault multi-account + OAuth refresh + default promotion, 0600 | done | `boot/auth_vault.py` `_SECURE_FILE_MODE=0o600` (chmod on every rewrite); refresh at 60 s expiry margin; legacy-shape tolerance · `tests/launch/test_launch.py` vault cases |
|
|
140
|
+
| `primeProviderEnv`/key-resolver parity | done | `boot/runners/session.py` `prime_provider_env` (PROVIDER_ENV map) + `build_key_resolver` (`--account` → default → first) · `tests/boot/test_session_persist.py` |
|
|
141
|
+
| Window-budget 0.75/6000/2048 + manual `/summarize-context` w/ guidance | done | `window_budget/condenser.py` `DEFAULT_POLICY(trigger_ratio=0.75, keep_recent=6000, reserve_tokens=2048)`; 3.6 chars/token (TS value, not framework 4.0); identity-on-under-budget asserted with `is`/`is not` · `tests/window_budget/test_window_budget.py` |
|
|
142
|
+
| Insight wrapper round-trip (collector + replay) | done | `insight/` wraps `indusagi.tracing` (Probe→Segment vocabulary; `v:1` NDJSON + `[insight:scrubbed]` pinned in wrapper); collector sink + replay reader are the app-owned pieces · `tests/insight/test_insight.py` (round-trip, FNV-1a gate, redaction + 4096 truncation) |
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Deliberate waivers / documented divergences (all plan-cited)
|
|
147
|
+
|
|
148
|
+
| # | Waiver | Citation |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| 1 | **Composer deleted** — TS software-caret `Composer.tsx` replaced by framework `PromptEditor` | PLAN.md §1 line 24, §2 line 104; analysis 02 §6 |
|
|
151
|
+
| 2 | **Paste vault/burst-debounce deleted** — replaced by framework `EditorCore` paste markers + native `events.Paste` (`paste.ts` gone) | PLAN.md §1 line 24, §3 line 177; analysis 02 §6 |
|
|
152
|
+
| 3 | **jiti → importlib** — addons are `.py`, loaded via `importlib.util.spec_from_file_location`; virtual-modules/alias bridge collapses, `BUNDLED_NAMESPACES` vestigial | analysis 03 lines 126, 153, 199; PLAN.md line 79 |
|
|
153
|
+
| 4 | **insight wraps `indusagi.tracing`** — not re-ported (2.5k LOC stays in framework); only collector sink, replay reader, FNV-1a gate are app code | PLAN.md line 24 ("locked"), line 165; analysis 05 §3 |
|
|
154
|
+
| 5 | **repl placeholder gone** — M0's `cli.py` stub runner (PLAN.md line 200-201) replaced in M5 by the real Textual console mount (`boot/runners/repl_runner.py` defaults to `induscode.console.mount.mount_console`; seam stays injectable for tests) | PLAN.md line 237 ("repl-runner wired") |
|
|
155
|
+
| 6 | **runtime-bridge not wired into boot** — parity with TS, where it is also a barrel-only library layer | analysis 01 §1 line 12 (quoted verbatim above) |
|
|
156
|
+
| 7 | **`/memory` cosmetic parity** — `on/off` flips a reporting flag only, never removes the memory card from the live deck; `# parity:` comment at `integrations.py:122-124` | PLAN.md lines 164, 259 (quirk preservation); analysis 03 line 196 |
|
|
157
|
+
| 8 | **Two compaction engines stay distinct** — `window_budget` (tokens, 0.75) is the conductor's CondenseFn; framework `runtime.memory` (turns, 0.8) never wired in | PLAN.md lines 166, 255; analysis 05 §1/risk-3 |
|
|
158
|
+
| 9 | **Chord-latch break semantics** — TS broke a primed double-tap on any key; the Textual editor consumes printables, so the latch is timer-bounded (600 ms window and outcomes identical); documented at `console/app.py` | analysis 02 (input collapse); divergence note in code |
|
|
159
|
+
| 10 | **OAuth overlay redesigned on `asyncio.Future`** — parked-resolver instead of TS callback threading; flow integrity (provider threading, vault refresh) preserved | PLAN.md M5 line 237; analysis 02 risk-1 |
|
|
160
|
+
| 11 | **TypeBox → literal JSON-schema dicts** — framework `parameters: Mapping` convention; runtime guards for `action` discriminants | PLAN.md line 176; analysis 03 §6.7 |
|
|
161
|
+
| 12 | **Modal routing inversion** — TS always-mounted overlay components → awaited `push_screen` flows with typed outcomes; reducer modal bookkeeping preserved | PLAN.md M5 line 237 ("overlays as push_screen flows") |
|
|
162
|
+
| 13 | **todo wire names** — Python framework exposes `todo_read`/`todo_set` (TS framework: `todoread`/`todowrite`); the deck honors the framework wire contract verbatim | builtin_bridge.py:307-310; PLAN.md framework-convention rule (line 176) |
|
|
163
|
+
|
|
164
|
+
## Plan-doc errata (no port action needed)
|
|
165
|
+
|
|
166
|
+
1. **§6 "settings (16 rows)"** — TS `pickers.tsx:418-542` defines **15** rows; Python matches TS (15). The plan overstated by one.
|
|
167
|
+
2. **§6 `--tools` "(… `_`/`-`-insensitive)" attributed to `launch/invocation/flags.py`** — in both TS and Python the canonicalization lives in the boot session runner's deck filter (`session.ts:261` / `session.py:314-330`); the parser-level roster filter is exact-match in both. Behavior parity is exact, including the empty-allowlist→all-tools quirk.
|
|
168
|
+
|
|
169
|
+
## Follow-ups (non-blocking)
|
|
170
|
+
|
|
171
|
+
1. **[upstream / indusagi-python-rebuild]** Fix `_FacadeTranslator._on_delta` first-delta framing (root cause above) so the first streamed chunk reaches `MessageUpdateEvent`; then re-run smoke (a) expecting exactly `pong`. Add a framework regression test: first text delta must surface as a `text_delta` update.
|
|
172
|
+
2. Once fixed upstream, add an induscode integration pin: oneshot `-p` output equals the full assistant text for a multi-chunk stream.
|
|
173
|
+
3. Optional: dedicated per-method timeout test for the dialog bridge's 90 s fallback (currently covered via the linked-server round-trip suite).
|
induscode-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: induscode
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Indusagi coding agent — terminal-first AI coding agent on the indusagi framework (Python rebuild)
|
|
5
|
+
Project-URL: Homepage, https://github.com/varunisrani/indusagi-ts
|
|
6
|
+
Project-URL: Repository, https://github.com/varunisrani/indusagi-ts
|
|
7
|
+
Project-URL: Issues, https://github.com/varunisrani/indusagi-ts/issues
|
|
8
|
+
Author: Varun Israni
|
|
9
|
+
Author-email: IndusAGI Team <team@indusagi.ai>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: CREDITS.md
|
|
12
|
+
License-File: NOTICE
|
|
13
|
+
Keywords: agent,ai,cli,coding-agent,llm,mcp,tui
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Requires-Dist: indusagi[mcp,tui]>=0.1.0
|
|
26
|
+
Requires-Dist: markdown-it-py>=3.0
|
|
27
|
+
Requires-Dist: pygments>=2.18
|
|
28
|
+
Requires-Dist: python-ulid>=2.7
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# induscode (Python rebuild)
|
|
35
|
+
|
|
36
|
+
Terminal-first AI coding agent on the [indusagi framework](../indusagi-python-rebuild)
|
|
37
|
+
— the Python port of the TypeScript `indusagi-coding-agent` (lineage v0.1.62).
|
|
38
|
+
Console scripts: **`pindus`** (primary) and **`induscode`** (alias).
|
|
39
|
+
|
|
40
|
+
Port plan: `../indus-code-rebuild/PYTHON_PORT_PLAN/PLAN.md` (milestones M0–M6).
|
|
41
|
+
Current status: **all milestones built** — conductor turn loop, runtime bridge,
|
|
42
|
+
capability deck, addons, headless CLI (launch/boot/channels/sessions), Textual
|
|
43
|
+
console with 26 slash commands, transcript export, and packaging. **649 tests
|
|
44
|
+
green**; lineage scan clean; wheel verified in a fresh venv.
|
|
45
|
+
|
|
46
|
+
## Install (from wheels)
|
|
47
|
+
|
|
48
|
+
The `indusagi` framework is a local dependency (not on PyPI), so install its
|
|
49
|
+
wheel first — with the `[mcp,tui]` extras, which the agent requires — then the
|
|
50
|
+
`induscode` wheel:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
python3.13 -m venv agent-venv
|
|
54
|
+
agent-venv/bin/pip install '../indusagi-python-rebuild/dist/indusagi-0.1.0-py3-none-any.whl[mcp,tui]'
|
|
55
|
+
agent-venv/bin/pip install dist/induscode-0.1.0-py3-none-any.whl
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Build the wheels with `python -m build` in each project if `dist/` is missing
|
|
59
|
+
or stale.
|
|
60
|
+
|
|
61
|
+
## Run
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pindus # interactive Textual console
|
|
65
|
+
pindus -p "explain this repo" # one-shot: print the result and exit
|
|
66
|
+
pindus -p --json "explain this repo" # headless NDJSON line protocol
|
|
67
|
+
pindus --list-models # provider/model catalog
|
|
68
|
+
pindus signin # store credentials (OAuth or API key)
|
|
69
|
+
pindus signin --list # show stored accounts
|
|
70
|
+
pindus --help # full flag table (@file attachments, --, etc.)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
State lives in **`~/.pindusagi`** (shared with the framework; the
|
|
74
|
+
`INDUSAGI_CODING_AGENT_DIR` then `INDUSAGI_HOME` environment variables
|
|
75
|
+
override). Credentials are stored in the agent's multi-account vault
|
|
76
|
+
(`auth.json`, mode 0600).
|
|
77
|
+
|
|
78
|
+
## Dev setup
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
python3.13 -m venv .venv
|
|
82
|
+
.venv/bin/pip install -e ../indusagi-python-rebuild -e ".[dev]"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Gates
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
.venv/bin/pytest -q # test suite — 649 tests, never weakened
|
|
89
|
+
.venv/bin/python scripts/lineage_scan.py # clean-room lineage guard
|
|
90
|
+
.venv/bin/pindus --version # smoke: prints "induscode 0.1.0"
|
|
91
|
+
.venv/bin/python -m build # sdist + wheel into dist/
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The packaging gate additionally installs both wheels into a throwaway venv and
|
|
95
|
+
runs `--version`, `--help`, `--list-models`, and the subsystem imports.
|
|
96
|
+
|
|
97
|
+
MIT © Varun Israni — see `NOTICE` and `CREDITS.md`.
|