fix: read Git's absolute git dir so a Windows worktree is not the base tree - #45
Merged
Merged
Conversation
…e tree Git for Windows answers `rev-parse --git-dir` with a drive path (`D:/repo/.git/worktrees/x`). That is absolute there but starts with no slash, so `worktree_kind` read it as relative, prefixed the root, and never found `commondir` — every task worktree was reported as the base working tree. `promote` refused to run and `intake` warned about a tree it had not touched. Reading `--absolute-git-dir` instead gives a path that is usable on every host, so the prefixing logic goes away entirely. Verified on Linux by injecting the Windows shape: `D:/` is a legal relative path there, so a fixture under `D:/repo/...` plus a git shim that answers with a drive path reproduces the old failure. Reverting this change makes the new smoke assertion fail.
…ree in the Windows suite `D:/` is a relative path on Linux — a directory literally named `D:` — so a fixture under `D:/repo/.git/worktrees/<name>/` plus a `git` shim that answers `--git-dir` and `--absolute-git-dir` with a drive path reproduces the Windows failure without a Windows host. The shim has to answer both flags: forwarding only `--absolute-git-dir` lets the old implementation reach the real git and fail for an unrelated reason, which is a test that passes while proving nothing. Adds the real-host counterpart too: the PowerShell suite now initializes a repository, adds a linked worktree, and asserts that `promote` goes through there while the base tree is still refused — a fix that answered "task worktree" everywhere would pass the first half alone. Its Todo lookup now finds the entry by goal instead of assuming the first `- ID:` line is the one just written.
…daily The index renderer stamped today's date into every row and into the header on each SessionStart, so "last checked" only ever said "a session started today". Nothing read it: the carry-over scan discarded the column, `repository-check` only tests that the index exists, and no test asserted on it. What it did do was rewrite a tracked file on the first session of every calendar day, which is the uncommitted-but-tracked state that makes the next checkout refuse to move. The renderer's own `cmp -s` guard could not help because the date was part of the content it compared. The table keeps Class, Source path, Phases, Exists, and Status. Carry-over still reads rows written before the drop — they are one cell longer and the status is still last — and now requires that last cell to look like a status, so a date can never be mistaken for one in a row that lost its status cell.
The Windows worktree fix and the index date-column removal landed in this branch, so the task documents that authorized them land with it: the PRD for the confirmed facts and scope, and the Plan with the user's approval and the verification record, including the mutation runs and the honest gap — the PowerShell regression cannot be run on this host.
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds DeepSeek Harness (dsh) as a fourth host. The repository root becomes a dsh plugin package, so a dsh user installs the skill and the SessionStart hook with one command and no hand-written profile YAML:
package.jsondeclaresdsh.bundle.patch— the marker dsh reads — anddsh/index.jsmounts dsh's own@deepseek-ai/dsh-skill-filesystemover this package'sskills/and@deepseek-ai/dsh-hooks-claude-codeover the newhooks/hooks-dsh.json. Both are dsh's packages rather than copies: duplicating the skill provider would duplicate frontmatter parsing, watching and catalog caching; duplicating the bridge would duplicate the matcher and the output codec.Three measured facts shape the wiring. Each was verified against an installed dsh 0.1.5-rc.2 rather than taken from documentation, and each is the reason for a choice that would otherwise look arbitrary:
baseUrlis the profile directory, not the plugin's. No bundle patch can name the files that ship beside it — sodsh/cordis.patch.ymlis a single insert with no config, and every path lives indsh/index.js, derived fromimport.meta.url.${CLAUDE_PLUGIN_ROOT}inside the command string but never exports it.hooks/session-startpicks its output shape from the environment, so without theCLAUDE_PLUGIN_ROOT=prefix the hook takes its fallback branch, emits a top-leveladditionalContext, and the bridge's codec discards it silently — exit 0, valid JSON, no context. That prefix is the entire reasonhooks-dsh.jsonis a separate file instead of reusinghooks/hooks.json.SessionStartSourceisstartup | resume | clear | compact. There is nofork, so the dsh matcher omits it.No hook script changes.
hooks/session-startis byte-identical; the dsh wiring adapts to the scripts, not the reverse. The other three hosts' wiring files are untouched.TaskFlow traceability
TaskFlowDocs/2026-09-19-dsh-host/package.json;dsh/index.js;dsh/cordis.patch.yml;hooks/hooks-dsh.json; host lists inREADME.md,README.zh-CN.md,hooks/README.md,skills/taskflow/references/runtime.md,ROADMAP.md; the release-literal set inRELEASE.md,hooks/release-check,CHANGELOG.md,hooks/smoke-test. Approved Plan:TaskFlowDocs/2026-09-19-dsh-host/plan.md(v1, approved 2026-09-19 19:29 +08:00).mainhkwuks/TaskFlowVerification
bash hooks/smoke-test→ALL SMOKE PASSEDgit diff --check→ cleanComposition layer.
dsh --profile <p> --dump-config→ exit 0, with- id: taskflow / name: dsh-taskflowcomposited over the base and web-app layers. Verified through the literal documented commanddsh plugin --profile <p> add <repo-root>, not a hand-edited profile.Mount layer. Booting that profile with an independent observer plugin read
ctx.skills.list()and got exactly["taskflow|taskflow|custom"]— one entry, from the provider this plugin registers.Seam layer. Driving dsh's own libraries over the hook this repository actually ships:
matchesMatcheracceptsstartup,resume,clear,compactand rejectsfork,end, and the empty string.parseHookOutputreads the hook's real stdout back as 397 characters of SessionStart context.PreToolUseyields nothing (proving thehookEventNameguard admitted it), and dropping theCLAUDE_PLUGIN_ROOTprefix loses the context (proving that prefix is load-bearing, not decorative).Repository checks.
bash hooks/release-check .→STATUS: passwith the fourth manifest in the compared set.bash hooks/repository-check .→needs-user-input, naming this task's uncommitted directory at the time it ran — expected mid-task, and its exit code is unchanged by that report.Mutation check on the new coverage. The smoke-test section added here was verified to fail on both mutations it exists to catch: removing the
CLAUDE_PLUGIN_ROOTprefix, and pointing the command at a root with no hooks.No API call was made; the approved scope excluded a live model run.
Review boundaries
Base and remote.
originishttps://github.com/hkwuks/TaskFlow.git, branchfeature/dsh-host, cut frommainat7dcff62with no divergence at cut time.mainwas not merged into this branch during the work.Two files in this PR are not part of the dsh change, and both are worth a look rather than a rubber stamp:
TaskFlowDocs/repository-docs/index.md— the SessionStart hook regenerating its own metadata dates. The identical diff exists independently in the base checkout, byte-for-byte; it is not a manual edit and not divergent.TaskFlowDocs/todo.md— this task's own intake entry,TF-20260919-55110d. Another session added a different entry (TF-20260919-d6005c, on personal-rules layout) totodo.mdin the base checkout, which is not in this branch. Both are appends at the same anchor, so the merge driver handles the merge — but if that other entry is still uncommitted at merge time, it needs landing separately.Known limitations
additionalContext, a hook that times out — is out of reach of the verification above; the seam test drives dsh's real parser to narrow the gap, not to close it.hooks/hooks-dsh.jsonnames nocommandWindowsequivalent.ctx.loader.importis not a documented plugin-authoring surface. It is how dsh's ownloadProfileDirectorymounts nested entries, so it is not private to the launcher, but a future dsh release could change it.dsh-taskflowis unclaimed and the package could be published later, but nothing in this PR publishes, tags, or pins anything.