@devflow-core/dsh-devflow 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/NOTICE +13 -0
  2. package/README.md +82 -0
  3. package/assets/commands/devflow-adversarial.toml +11 -0
  4. package/assets/commands/devflow-audit.toml +32 -0
  5. package/assets/commands/devflow-debt.toml +42 -0
  6. package/assets/commands/devflow-find-fault.toml +11 -0
  7. package/assets/commands/devflow-learn.toml +21 -0
  8. package/assets/commands/devflow-plan.toml +58 -0
  9. package/assets/commands/devflow-prove.toml +20 -0
  10. package/assets/commands/devflow-pua.toml +40 -0
  11. package/assets/commands/devflow-review.toml +36 -0
  12. package/assets/commands/devflow-spec.toml +49 -0
  13. package/assets/commands/devflow.toml +35 -0
  14. package/assets/presets/devflow-2/NOTICE +4 -0
  15. package/assets/presets/devflow-2/README.md +71 -0
  16. package/assets/presets/devflow-2/agent.cordis.yml +337 -0
  17. package/assets/presets/devflow-2/custom-bash.mjs +213 -0
  18. package/assets/presets/devflow-2/preset.yml +3 -0
  19. package/assets/presets/devflow-2/tool-bootstrap.mjs +496 -0
  20. package/assets/scripts/devflow-audit.js +275 -0
  21. package/assets/scripts/devflow-debt.js +196 -0
  22. package/assets/scripts/devflow-doctor.js +90 -0
  23. package/assets/scripts/devflow-plan.js +638 -0
  24. package/assets/scripts/devflow-review.js +93 -0
  25. package/assets/scripts/devflow-spec.js +238 -0
  26. package/assets/skills/devflow-adversarial/SKILL.md +71 -0
  27. package/assets/skills/devflow-audit/SKILL.md +78 -0
  28. package/assets/skills/devflow-brainstorm/SKILL.md +176 -0
  29. package/assets/skills/devflow-brainstorm/references/interview-discipline.md +184 -0
  30. package/assets/skills/devflow-build/SKILL.md +238 -0
  31. package/assets/skills/devflow-build/references/build-methods.md +40 -0
  32. package/assets/skills/devflow-core/SKILL.md +93 -0
  33. package/assets/skills/devflow-core/references/core-methods.md +131 -0
  34. package/assets/skills/devflow-core/references/reference-projects.md +133 -0
  35. package/assets/skills/devflow-core/references/skill-guide.md +63 -0
  36. package/assets/skills/devflow-cut/SKILL.md +208 -0
  37. package/assets/skills/devflow-cut/references/cut-methods.md +65 -0
  38. package/assets/skills/devflow-cut/references/native-capability-checklist.md +112 -0
  39. package/assets/skills/devflow-docs-followup/SKILL.md +132 -0
  40. package/assets/skills/devflow-docs-followup/agents/openai.yaml +4 -0
  41. package/assets/skills/devflow-find-fault/SKILL.md +109 -0
  42. package/assets/skills/devflow-learn/SKILL.md +176 -0
  43. package/assets/skills/devflow-plan/SKILL.md +142 -0
  44. package/assets/skills/devflow-plan/references/plan-methods.md +74 -0
  45. package/assets/skills/devflow-project-knowledge/SKILL.md +354 -0
  46. package/assets/skills/devflow-prove/SKILL.md +216 -0
  47. package/assets/skills/devflow-prove/references/code-review-checklist.md +202 -0
  48. package/assets/skills/devflow-prove/references/flow-self-test.md +775 -0
  49. package/assets/skills/devflow-prove/references/proof-recovery-methods.md +26 -0
  50. package/assets/skills/devflow-pua/SKILL.md +197 -0
  51. package/assets/skills/devflow-pua/references/flavor-display.md +49 -0
  52. package/assets/skills/devflow-pua/references/methodology-library.md +193 -0
  53. package/assets/skills/devflow-pua/references/methodology-router.md +78 -0
  54. package/assets/skills/devflow-spec/SKILL.md +92 -0
  55. package/assets/skills/devflow-spec/references/spec-plan-methods.md +15 -0
  56. package/cordis.patch.yml +11 -0
  57. package/lib/dsh-home.js +33 -0
  58. package/lib/index.js +79 -0
  59. package/lib/mount-once.js +34 -0
  60. package/lib/sync.js +168 -0
  61. package/package.json +32 -0
@@ -0,0 +1,33 @@
1
+ // @devflow-core/dsh-devflow — DSH home resolution.
2
+ // Adapted from @linxin666/dsh-liangshen/src/dsh-home.ts (Apache-2.0):
3
+ // the environment override wins, the platform home fallback follows.
4
+
5
+ import { homedir } from 'node:os'
6
+ import { isAbsolute, join } from 'node:path'
7
+
8
+ /** Expand a leading ~ (or ~user) in a path, platform-style. */
9
+ export function expandHome(path, home = homedir()) {
10
+ if (path === '~') return home
11
+ if (path.startsWith('~/') || path.startsWith('~\\')) return join(home, path.slice(2))
12
+ return path
13
+ }
14
+
15
+ /**
16
+ * Resolve the DSH home directory.
17
+ * @param env - process environment to read DSH_HOME from.
18
+ * @param home - platform home directory fallback (test seam).
19
+ * @returns the absolute DSH home path.
20
+ */
21
+ export function resolveDshHome(env = process.env, home = homedir()) {
22
+ const raw = env.DSH_HOME
23
+ if (raw !== undefined && raw.trim() !== '') {
24
+ const expanded = expandHome(raw.trim(), home)
25
+ return isAbsolute(expanded) ? expanded : join(process.cwd(), expanded)
26
+ }
27
+ return join(home, '.dsh')
28
+ }
29
+
30
+ /** Resolve the DSH home directory from the live environment. */
31
+ export function dshHome() {
32
+ return resolveDshHome()
33
+ }
package/lib/index.js ADDED
@@ -0,0 +1,79 @@
1
+ // @devflow-core/dsh-devflow — DevFlow asset preset plugin for the dsh web GUI.
2
+ //
3
+ // Host half only: on startup it syncs the bundled DevFlow assets (agent
4
+ // preset devflow-2, skills, commands, verification scripts) into the
5
+ // harness-home runtime roots (~/.dsh/.agent-presets, skills, commands,
6
+ // scripts), making the DevFlow preset selectable for new sessions and the
7
+ // DevFlow skills/commands/checkers available without copying files by hand,
8
+ // and announces the capability through a system-prompt section. No browser
9
+ // half, no routes, no agent tools — the preset itself provides the tools.
10
+ //
11
+ // Adapted from @linxin666/dsh-liangshen (Apache-2.0): two-phase anchored
12
+ // preset distribution + startup sync + system-prompt announcement.
13
+
14
+ import { fileURLToPath } from 'node:url'
15
+ import { dshHome } from './dsh-home.js'
16
+ import { mountOnce } from './mount-once.js'
17
+ import { syncAllAssets } from './sync.js'
18
+
19
+ /** Stable cordis plugin name. */
20
+ export const name = 'devflow'
21
+
22
+ /** Order of the announcement section within the tool-guidance band. */
23
+ const SECTION_ORDER = 150
24
+
25
+ /** Absolute path of the bundled assets tree inside this package. */
26
+ export function bundledAssetsRoot() {
27
+ return fileURLToPath(new URL('../assets/', import.meta.url))
28
+ }
29
+
30
+ /** Model-facing announcement: plugin presence, preset, and conflict policy. */
31
+ export const DEVFLOW_GUIDANCE =
32
+ '本机已安装 @devflow-core/dsh-devflow 插件(DevFlow agent preset 分发):新建会话的预设选择器中可选「DevFlow 2.0 (Anchored)」(两阶段锚定 + Code Mode)。插件启动时把 devflow-2 预设、devflow-* skills、devflow*.toml 命令与 devflow-*.js 验证脚本同步到 ~/.dsh/(.agent-presets/skills/commands/scripts);冲突策略为 devflow-* 权威覆盖(字节相同跳过),非 devflow 资产永不触碰;升级插件后重启即自动更新。用户提到「DevFlow / devflow-2 / 锚定模式」时即指本插件,请据此协作。'
33
+
34
+ /**
35
+ * Mount the plugin: sync bundled DevFlow assets into the harness-home
36
+ * runtime roots, then announce through a system-prompt section.
37
+ * @param ctx - host plugin context (cordis).
38
+ */
39
+ export const apply = mountOnce('@devflow-core/dsh-devflow', (ctx) => {
40
+ const sync = () => {
41
+ const home = dshHome()
42
+ try {
43
+ const result = syncAllAssets(bundledAssetsRoot(), home)
44
+ for (const { id, error } of result.failed) {
45
+ ctx?.logger?.warn?.(`dsh-devflow: ${id} sync failed: ${error}`)
46
+ }
47
+ if (result.synced.length > 0) {
48
+ ctx?.logger?.info?.(`dsh-devflow: assets synced into ${home}: ${result.synced.join(', ')}`)
49
+ }
50
+ if (result.pruned.length > 0) {
51
+ ctx?.logger?.info?.(`dsh-devflow: pruned stale assets from ${home}: ${result.pruned.join(', ')}`)
52
+ }
53
+ } catch (error) {
54
+ ctx?.logger?.warn?.(`dsh-devflow: asset sync failed: ${error instanceof Error ? error.message : String(error)}`)
55
+ }
56
+ }
57
+
58
+ sync()
59
+
60
+ // Optional announcement: register only when the systemPrompt service is
61
+ // available; a missing service must not block the sync (no hard inject).
62
+ let disposeSection
63
+ try {
64
+ const systemPrompt = ctx?.get?.('systemPrompt')
65
+ if (systemPrompt && typeof systemPrompt.section === 'function') {
66
+ disposeSection = systemPrompt.section({
67
+ name: 'plugin:dsh-devflow',
68
+ order: SECTION_ORDER,
69
+ text: DEVFLOW_GUIDANCE,
70
+ })
71
+ }
72
+ } catch {
73
+ disposeSection = undefined
74
+ }
75
+
76
+ ctx?.effect?.(() => () => {
77
+ if (typeof disposeSection === 'function') disposeSection()
78
+ }, 'dsh-devflow: announcement')
79
+ })
@@ -0,0 +1,34 @@
1
+ // @devflow-core/dsh-devflow — host single-instance guard.
2
+ // Adapted from @linxin666/dsh-liangshen/src/mount-once.ts (Apache-2.0).
3
+ // The registry rides a global symbol so two module instances of the same
4
+ // package (npm copy vs repository link) still share one verdict. This plugin
5
+ // uses its own symbol namespace (`dsh-devflow.mounted`) so it never collides
6
+ // with the dsh-web-ui family registry.
7
+
8
+ const MOUNTED = Symbol.for('dsh-devflow.mounted')
9
+
10
+ function mountedSet() {
11
+ const registry = globalThis
12
+ return (registry[MOUNTED] ??= new Set())
13
+ }
14
+
15
+ /**
16
+ * Wrap a cordis plugin apply so the package runs at most once per process.
17
+ * The first mount registers normally and unmarks when its fiber disposes;
18
+ * any later mount of the same package name is a no-op.
19
+ * @param packageName - npm package identity shared by every install source.
20
+ * @param fn - the original plugin apply.
21
+ * @returns an apply of the same shape.
22
+ */
23
+ export function mountOnce(packageName, fn) {
24
+ return (...args) => {
25
+ const mounted = mountedSet()
26
+ if (mounted.has(packageName)) return
27
+ mounted.add(packageName)
28
+ const ctx = args[0]
29
+ ctx?.effect?.(() => () => {
30
+ mounted.delete(packageName)
31
+ })
32
+ return fn(...args)
33
+ }
34
+ }
package/lib/sync.js ADDED
@@ -0,0 +1,168 @@
1
+ // @devflow-core/dsh-devflow — asset sync engine.
2
+ // Adapted from @linxin666/dsh-liangshen/src/sync.ts (Apache-2.0), narrowed to
3
+ // the four DevFlow asset groups and the "devflow-* authoritative override"
4
+ // conflict policy the user chose: byte-identical files are skipped, differing
5
+ // files are overwritten, and only target files whose name matches the group's
6
+ // own prefix (devflow-*) and no longer exist in the source are pruned. Files
7
+ // outside the group prefix are never touched.
8
+
9
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, utimesSync } from 'node:fs'
10
+ import { dirname, join, relative } from 'node:path'
11
+
12
+ /**
13
+ * Clock/coarse-grain tolerance for the mtime fast path. When a source and a
14
+ * target file share a size and a near-identical mtime we still fall through to
15
+ * a byte comparison; a mtime gap beyond this simply proves the pair cannot be
16
+ * byte-identical, so we skip the read.
17
+ */
18
+ const MTIME_TOLERANCE_MS = 1000
19
+
20
+ /** One sync run's outcome, grouped for diagnostics. */
21
+ export function newSyncResult() {
22
+ return { synced: [], current: [], failed: [], pruned: [] }
23
+ }
24
+
25
+ function filesUnder(root) {
26
+ const out = []
27
+ const walk = (dir) => {
28
+ for (const entry of readdirSync(dir)) {
29
+ const path = join(dir, entry)
30
+ if (statSync(path).isDirectory()) walk(path)
31
+ else out.push(path)
32
+ }
33
+ }
34
+ walk(root)
35
+ return out
36
+ }
37
+
38
+ /**
39
+ * File identity is bytes. Size and mtime are only a fast negative check: a
40
+ * size mismatch or a mtime gap beyond the tolerance proves the pair cannot be
41
+ * byte-identical without reading both, but an equal size and close mtime still
42
+ * fall through to a byte comparison so content differences are never missed.
43
+ */
44
+ function sameFile(a, b) {
45
+ const sourceStat = statSync(a)
46
+ const targetStat = statSync(b)
47
+ if (sourceStat.size !== targetStat.size) return false
48
+ if (Math.abs(sourceStat.mtimeMs - targetStat.mtimeMs) > MTIME_TOLERANCE_MS) return false
49
+ return readFileSync(a).equals(readFileSync(b))
50
+ }
51
+
52
+ /**
53
+ * Copy the whole tree under `sourceDir` into `targetDir`, creating the target
54
+ * directory as needed. Intentionally not `fs.cpSync` (recursive): on Node 22
55
+ * for Windows, `fs.cpSync` with `recursive: true` crashes the process with a
56
+ * fatal error (STATUS_STACK_BUFFER_OVERRUN / 0xC0000409, no JS exception is
57
+ * thrown) whenever the source path contains non-ASCII characters such as a
58
+ * CJK home directory (nodejs/node#54476). Source mtimes are preserved.
59
+ */
60
+ function copyTreeSync(sourceDir, targetDir) {
61
+ mkdirSync(targetDir, { recursive: true })
62
+ for (const entry of readdirSync(sourceDir)) {
63
+ const source = join(sourceDir, entry)
64
+ const target = join(targetDir, entry)
65
+ const stat = statSync(source)
66
+ if (stat.isDirectory()) {
67
+ copyTreeSync(source, target)
68
+ } else {
69
+ copyFileSync(source, target)
70
+ utimesSync(target, stat.atime, stat.mtime)
71
+ }
72
+ }
73
+ }
74
+
75
+ /** Remove one target entry (file or dir) if it exists. */
76
+ function removeEntry(target) {
77
+ if (!existsSync(target)) return
78
+ rmSync(target, { recursive: true, force: true })
79
+ }
80
+
81
+ /**
82
+ * Sync one source group into its target directory.
83
+ *
84
+ * @param sourceDir - bundled group root inside the package (e.g. assets/skills).
85
+ * @param targetDir - user runtime group root (e.g. <home>/skills).
86
+ * @param options - { prefix } the group's owned name prefix (e.g. 'devflow-').
87
+ * Only entries matching the prefix are copied from source or pruned from
88
+ * target; everything else in either directory is left untouched.
89
+ * @param report - SyncResult accumulator; pushes the group id when changed.
90
+ * @param groupId - diagnostic id for the accumulator.
91
+ */
92
+ export function syncGroup(sourceDir, targetDir, { prefix }, report, groupId) {
93
+ if (!existsSync(sourceDir)) {
94
+ report.failed.push({ id: groupId, error: `bundled source missing: ${sourceDir}` })
95
+ return
96
+ }
97
+ mkdirSync(targetDir, { recursive: true })
98
+
99
+ const sourceEntries = readdirSync(sourceDir).filter((name) => name.startsWith(prefix))
100
+ let dirty = false
101
+
102
+ // Copy or refresh every owned source entry, then prune owned target entries
103
+ // the source no longer ships. Byte-identical files stay untouched so the
104
+ // user's mtimes and any local tooling watching them are preserved.
105
+ for (const name of sourceEntries) {
106
+ const source = join(sourceDir, name)
107
+ const target = join(targetDir, name)
108
+ const stat = statSync(source)
109
+ if (stat.isDirectory()) {
110
+ const existing = existsSync(target) && statSync(target).isDirectory()
111
+ if (existing && sameTree(source, target)) continue
112
+ removeEntry(target)
113
+ copyTreeSync(source, target)
114
+ dirty = true
115
+ } else {
116
+ if (existsSync(target) && !statSync(target).isDirectory() && sameFile(source, target)) continue
117
+ removeEntry(target)
118
+ mkdirSync(dirname(target), { recursive: true })
119
+ copyFileSync(source, target)
120
+ utimesSync(target, stat.atime, stat.mtime)
121
+ dirty = true
122
+ }
123
+ }
124
+
125
+ for (const entry of readdirSync(targetDir)) {
126
+ if (!entry.startsWith(prefix)) continue
127
+ if (sourceEntries.includes(entry)) continue
128
+ removeEntry(join(targetDir, entry))
129
+ report.pruned.push(`${groupId}/${entry}`)
130
+ dirty = true
131
+ }
132
+
133
+ if (dirty) report.synced.push(groupId)
134
+ else report.current.push(groupId)
135
+ }
136
+
137
+ /** Byte-compare two directory trees without touching either. */
138
+ function sameTree(a, b) {
139
+ const aFiles = filesUnder(a)
140
+ const bFiles = filesUnder(b)
141
+ const aSet = new Set(aFiles.map((file) => relative(a, file)))
142
+ if (aFiles.length !== bFiles.length) return false
143
+ for (const file of aFiles) {
144
+ const rel = relative(a, file)
145
+ const peer = join(b, rel)
146
+ if (!existsSync(peer) || statSync(peer).isDirectory()) return false
147
+ if (!sameFile(file, peer)) return false
148
+ }
149
+ for (const file of bFiles) {
150
+ if (!aSet.has(relative(b, file))) return false
151
+ }
152
+ return true
153
+ }
154
+
155
+ /**
156
+ * Sync every bundled DevFlow asset group into the DSH home.
157
+ * @param assetsRoot - package assets root (e.g. <pkg>/assets).
158
+ * @param home - resolved DSH home directory.
159
+ * @returns a SyncResult with per-group outcomes.
160
+ */
161
+ export function syncAllAssets(assetsRoot, home) {
162
+ const report = newSyncResult()
163
+ syncGroup(join(assetsRoot, 'presets/devflow-2'), join(home, '.agent-presets/devflow-2'), { prefix: '' }, report, 'presets/devflow-2')
164
+ syncGroup(join(assetsRoot, 'skills'), join(home, 'skills'), { prefix: 'devflow-' }, report, 'skills')
165
+ syncGroup(join(assetsRoot, 'commands'), join(home, 'commands'), { prefix: 'devflow' }, report, 'commands')
166
+ syncGroup(join(assetsRoot, 'scripts'), join(home, 'scripts'), { prefix: 'devflow-' }, report, 'scripts')
167
+ return report
168
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@devflow-core/dsh-devflow",
3
+ "version": "0.1.0",
4
+ "description": "DevFlow for DeepSeek Harness: devflow-2 agent preset + skills + commands + verification scripts, synced into ~/.dsh on host startup.",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": "^22.19.0 || >=24.0.0"
8
+ },
9
+ "main": "./lib/index.js",
10
+ "exports": {
11
+ ".": "./lib/index.js",
12
+ "./package.json": "./package.json",
13
+ "./assets/*": "./assets/*"
14
+ },
15
+ "dsh": {
16
+ "bundle": {
17
+ "patch": "./cordis.patch.yml"
18
+ }
19
+ },
20
+ "files": [
21
+ "lib",
22
+ "assets",
23
+ "cordis.patch.yml",
24
+ "README.md",
25
+ "NOTICE"
26
+ ],
27
+ "license": "Apache-2.0",
28
+ "scripts": {
29
+ "sync-assets": "node scripts/sync-assets.js",
30
+ "test": "node test/sync.test.js"
31
+ }
32
+ }