@hasna/instructions 0.4.13 → 0.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -2641,9 +2641,6 @@ function templateizeMachineContent(content, machine) {
2641
2641
  next = next.replace(/~\/workspace/g, "{{WORKSPACE_ROOT}}").replace(/~\/Workspace/g, "{{WORKSPACE_ROOT}}").replace(/\/opt\/homebrew\/bin\/bun/g, "{{BUN_PATH}}");
2642
2642
  return { content: next, changed: next !== content };
2643
2643
  }
2644
- function renderMachineAwareContent(content, variables) {
2645
- return isTemplate(content) ? renderTemplate(content, variables) : content;
2646
- }
2647
2644
  function renderMachineAwareContentPreview(content, variables) {
2648
2645
  return isTemplate(content) ? renderTemplatePreview(content, variables) : { content, unresolved: [] };
2649
2646
  }
@@ -11577,8 +11574,9 @@ function normalizeTargetPath(p) {
11577
11574
  }
11578
11575
  }
11579
11576
  async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
11580
- const renderedTarget = opts.vars ? renderForApply(targetPath, opts.vars, opts.dryRun === true) : { content: targetPath, unresolved: [] };
11581
- const rendered = opts.vars ? renderForApply(content, opts.vars, opts.dryRun === true) : { content, unresolved: [] };
11577
+ const variables = opts.vars ?? {};
11578
+ const renderedTarget = renderForApply(targetPath, variables);
11579
+ const rendered = renderForApply(content, variables);
11582
11580
  const renderedTargetPath = renderedTarget.content;
11583
11581
  const renderedContent = rendered.content;
11584
11582
  const targetAgent = meta.agent ?? config.agent;
@@ -11617,13 +11615,42 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
11617
11615
  ...meta
11618
11616
  };
11619
11617
  }
11620
- function renderForApply(content, variables, preview) {
11621
- if (preview)
11622
- return renderMachineAwareContentPreview(content, variables);
11623
- return {
11624
- content: renderMachineAwareContent(content, variables),
11625
- unresolved: []
11626
- };
11618
+ function renderForApply(content, variables) {
11619
+ return renderMachineAwareContentPreview(content, variables);
11620
+ }
11621
+ function wouldDestroyACredential(targetPath, renderedContent, format) {
11622
+ const secretTokens = parseTemplateVars(renderedContent).filter(isSecretVarName);
11623
+ if (secretTokens.length === 0)
11624
+ return [];
11625
+ let current;
11626
+ try {
11627
+ const path = expandPath(targetPath);
11628
+ if (!existsSync7(path))
11629
+ return [];
11630
+ current = readFileSync4(path, "utf-8");
11631
+ } catch {
11632
+ return secretTokens;
11633
+ }
11634
+ if (current === renderedContent)
11635
+ return [];
11636
+ if (scanSecrets(current, format).length > 0)
11637
+ return secretTokens;
11638
+ return secretTokens.filter((name) => countPlaceholder(current, name) < countPlaceholder(renderedContent, name));
11639
+ }
11640
+ function countPlaceholder(content, name) {
11641
+ return content.match(new RegExp(`\\{\\{${name}(?::[^}]*)?\\}\\}`, "g"))?.length ?? 0;
11642
+ }
11643
+ async function resolveApplyVariables(opts) {
11644
+ const machine = detectMachineContext();
11645
+ let base;
11646
+ try {
11647
+ const store = opts.store ?? resolveConfigStore();
11648
+ const profile = await store.resolveProfileForMachine(machine);
11649
+ base = resolveProfileVariables(profile, machine);
11650
+ } catch {
11651
+ base = machineContextToVariables(machine);
11652
+ }
11653
+ return { ...base, ...opts.vars ?? {} };
11627
11654
  }
11628
11655
  function isAntigravityRuleTarget(agent, targetPath) {
11629
11656
  return agent === "antigravity" && /\.(md|mdc|markdown)$/i.test(targetPath);
@@ -11708,7 +11735,8 @@ async function applyConfigs(configs, opts = {}) {
11708
11735
  return report.results;
11709
11736
  }
11710
11737
  async function applyConfigsWithReport(configs, opts = {}) {
11711
- const prepared = prepareConfigBatch(configs, opts);
11738
+ const effective = { ...opts, vars: await resolveApplyVariables(opts) };
11739
+ const prepared = prepareConfigBatch(configs, effective);
11712
11740
  if (prepared.failures.length > 0) {
11713
11741
  return {
11714
11742
  results: [],
@@ -11722,7 +11750,7 @@ async function applyConfigsWithReport(configs, opts = {}) {
11722
11750
  if (config.kind === "reference" || isRetiredOrUnsupportedConfigAgent(config.agent))
11723
11751
  continue;
11724
11752
  try {
11725
- results.push(await applyPreparedConfig(config, opts));
11753
+ results.push(await applyPreparedConfig(config, effective));
11726
11754
  } catch (error) {
11727
11755
  failures.push({
11728
11756
  config_id: config.id,
@@ -11762,7 +11790,27 @@ function prepareConfigBatch(configs, opts) {
11762
11790
  }
11763
11791
  return false;
11764
11792
  });
11765
- const { configs: deduplicated, duplicates } = deduplicateEquivalentProfileConfigs(activeConfigs, opts);
11793
+ const renderSafeConfigs = activeConfigs.filter((config) => {
11794
+ const behavior = canonicalConfigApplyBehavior(config, opts, activeConfigs);
11795
+ const targets = [
11796
+ ...behavior.primary ? [{ path: behavior.primary.path, content: behavior.primary.content }] : [],
11797
+ ...behavior.outputs.map((output) => ({ path: output.path, content: output.content }))
11798
+ ];
11799
+ const blocked = [...new Set(targets.flatMap((target) => wouldDestroyACredential(target.path, target.content, config.format)))].sort();
11800
+ if (blocked.length === 0)
11801
+ return true;
11802
+ for (const target of targets) {
11803
+ skipped.push({
11804
+ config_id: config.id,
11805
+ config_slug: config.slug,
11806
+ path: target.path,
11807
+ owner: "unresolved-secret-placeholder",
11808
+ reason: `no value for ${blocked.map((name) => `{{${name}}}`).join(", ")} and the file holds something else there; writing the placeholder would overwrite a live credential`
11809
+ });
11810
+ }
11811
+ return false;
11812
+ });
11813
+ const { configs: deduplicated, duplicates } = deduplicateEquivalentProfileConfigs(renderSafeConfigs, opts);
11766
11814
  for (const duplicate of duplicates) {
11767
11815
  for (const target of configTargets(duplicate, opts, activeConfigs)) {
11768
11816
  skipped.push({
@@ -11914,6 +11962,8 @@ var init_apply = __esm(() => {
11914
11962
  init_machine();
11915
11963
  init_session_render();
11916
11964
  init_session_render_ownership();
11965
+ init_redact();
11966
+ init_template();
11917
11967
  init_transforms();
11918
11968
  });
11919
11969
 
@@ -15538,6 +15588,9 @@ program.command("apply <id>").description("Apply a config to its target_path and
15538
15588
  const status = opts.dryRun ? chalk.yellow("[dry-run]") : result.primary_changed ? chalk.green("\u2713") : chalk.dim("=");
15539
15589
  const change = result.primary_changed ? "changed" : "unchanged";
15540
15590
  console.log(`${status} ${result.path} ${chalk.dim(`(${change})`)}`);
15591
+ if (result.unresolved_template_vars?.length) {
15592
+ console.log(` ${chalk.yellow("!")} left unexpanded: ${result.unresolved_template_vars.map((name) => `{{${name}}}`).join(", ")}` + ` ${chalk.dim("(no value for these; secret placeholders are preserved on purpose)")}`);
15593
+ }
15541
15594
  for (const output of result.outputs ?? []) {
15542
15595
  const outputStatus = opts.dryRun ? chalk.yellow("[dry-run]") : output.primary_changed ? chalk.green("\u2713") : chalk.dim("=");
15543
15596
  const outputChange = output.primary_changed ? "changed" : "unchanged";
@@ -15818,11 +15871,10 @@ profileCmd.command("apply [id]").description("Apply all configs in a profile to
15818
15871
  for (const skipped of report.skipped) {
15819
15872
  console.log(`${chalk.dim("[owned]")} ${skipped.path} ${chalk.dim(skipped.owner)}`);
15820
15873
  }
15821
- if (opts.dryRun) {
15822
- const unresolved = [...new Set(results.flatMap((result) => result.unresolved_template_vars ?? []))];
15823
- if (unresolved.length > 0) {
15824
- console.log(chalk.yellow(`Unresolved secret/runtime template references preserved in preview: ${unresolved.join(", ")}`));
15825
- }
15874
+ const unresolved = [...new Set(results.flatMap((result) => result.unresolved_template_vars ?? []))];
15875
+ if (unresolved.length > 0) {
15876
+ const where = opts.dryRun ? "would be left unexpanded" : "left unexpanded";
15877
+ console.log(chalk.yellow(`Unresolved secret/runtime template references ${where}: ${unresolved.join(", ")}`));
15826
15878
  }
15827
15879
  for (const failure of report.failures) {
15828
15880
  console.error(chalk.red(`[failed] ${failure.config_slug}: ${failure.message}`));
package/dist/index.js CHANGED
@@ -9591,8 +9591,9 @@ function normalizeTargetPath(p) {
9591
9591
  }
9592
9592
  }
9593
9593
  async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
9594
- const renderedTarget = opts.vars ? renderForApply(targetPath, opts.vars, opts.dryRun === true) : { content: targetPath, unresolved: [] };
9595
- const rendered = opts.vars ? renderForApply(content, opts.vars, opts.dryRun === true) : { content, unresolved: [] };
9594
+ const variables = opts.vars ?? {};
9595
+ const renderedTarget = renderForApply(targetPath, variables);
9596
+ const rendered = renderForApply(content, variables);
9596
9597
  const renderedTargetPath = renderedTarget.content;
9597
9598
  const renderedContent = rendered.content;
9598
9599
  const targetAgent = meta.agent ?? config.agent;
@@ -9631,13 +9632,42 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
9631
9632
  ...meta
9632
9633
  };
9633
9634
  }
9634
- function renderForApply(content, variables, preview) {
9635
- if (preview)
9636
- return renderMachineAwareContentPreview(content, variables);
9637
- return {
9638
- content: renderMachineAwareContent(content, variables),
9639
- unresolved: []
9640
- };
9635
+ function renderForApply(content, variables) {
9636
+ return renderMachineAwareContentPreview(content, variables);
9637
+ }
9638
+ function wouldDestroyACredential(targetPath, renderedContent, format) {
9639
+ const secretTokens = parseTemplateVars(renderedContent).filter(isSecretVarName);
9640
+ if (secretTokens.length === 0)
9641
+ return [];
9642
+ let current;
9643
+ try {
9644
+ const path = expandPath(targetPath);
9645
+ if (!existsSync6(path))
9646
+ return [];
9647
+ current = readFileSync4(path, "utf-8");
9648
+ } catch {
9649
+ return secretTokens;
9650
+ }
9651
+ if (current === renderedContent)
9652
+ return [];
9653
+ if (scanSecrets(current, format).length > 0)
9654
+ return secretTokens;
9655
+ return secretTokens.filter((name) => countPlaceholder(current, name) < countPlaceholder(renderedContent, name));
9656
+ }
9657
+ function countPlaceholder(content, name) {
9658
+ return content.match(new RegExp(`\\{\\{${name}(?::[^}]*)?\\}\\}`, "g"))?.length ?? 0;
9659
+ }
9660
+ async function resolveApplyVariables(opts) {
9661
+ const machine = detectMachineContext();
9662
+ let base;
9663
+ try {
9664
+ const store = opts.store ?? resolveConfigStore();
9665
+ const profile = await store.resolveProfileForMachine(machine);
9666
+ base = resolveProfileVariables(profile, machine);
9667
+ } catch {
9668
+ base = machineContextToVariables(machine);
9669
+ }
9670
+ return { ...base, ...opts.vars ?? {} };
9641
9671
  }
9642
9672
  function isAntigravityRuleTarget(agent, targetPath) {
9643
9673
  return agent === "antigravity" && /\.(md|mdc|markdown)$/i.test(targetPath);
@@ -9722,7 +9752,8 @@ async function applyConfigs(configs, opts = {}) {
9722
9752
  return report.results;
9723
9753
  }
9724
9754
  async function applyConfigsWithReport(configs, opts = {}) {
9725
- const prepared = prepareConfigBatch(configs, opts);
9755
+ const effective = { ...opts, vars: await resolveApplyVariables(opts) };
9756
+ const prepared = prepareConfigBatch(configs, effective);
9726
9757
  if (prepared.failures.length > 0) {
9727
9758
  return {
9728
9759
  results: [],
@@ -9736,7 +9767,7 @@ async function applyConfigsWithReport(configs, opts = {}) {
9736
9767
  if (config.kind === "reference" || isRetiredOrUnsupportedConfigAgent(config.agent))
9737
9768
  continue;
9738
9769
  try {
9739
- results.push(await applyPreparedConfig(config, opts));
9770
+ results.push(await applyPreparedConfig(config, effective));
9740
9771
  } catch (error) {
9741
9772
  failures.push({
9742
9773
  config_id: config.id,
@@ -9776,7 +9807,27 @@ function prepareConfigBatch(configs, opts) {
9776
9807
  }
9777
9808
  return false;
9778
9809
  });
9779
- const { configs: deduplicated, duplicates } = deduplicateEquivalentProfileConfigs(activeConfigs, opts);
9810
+ const renderSafeConfigs = activeConfigs.filter((config) => {
9811
+ const behavior = canonicalConfigApplyBehavior(config, opts, activeConfigs);
9812
+ const targets = [
9813
+ ...behavior.primary ? [{ path: behavior.primary.path, content: behavior.primary.content }] : [],
9814
+ ...behavior.outputs.map((output) => ({ path: output.path, content: output.content }))
9815
+ ];
9816
+ const blocked = [...new Set(targets.flatMap((target) => wouldDestroyACredential(target.path, target.content, config.format)))].sort();
9817
+ if (blocked.length === 0)
9818
+ return true;
9819
+ for (const target of targets) {
9820
+ skipped.push({
9821
+ config_id: config.id,
9822
+ config_slug: config.slug,
9823
+ path: target.path,
9824
+ owner: "unresolved-secret-placeholder",
9825
+ reason: `no value for ${blocked.map((name) => `{{${name}}}`).join(", ")} and the file holds something else there; writing the placeholder would overwrite a live credential`
9826
+ });
9827
+ }
9828
+ return false;
9829
+ });
9830
+ const { configs: deduplicated, duplicates } = deduplicateEquivalentProfileConfigs(renderSafeConfigs, opts);
9780
9831
  for (const duplicate of duplicates) {
9781
9832
  for (const target of configTargets(duplicate, opts, activeConfigs)) {
9782
9833
  skipped.push({
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=apply-secret-placeholder-guard.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-secret-placeholder-guard.test.d.ts","sourceRoot":"","sources":["../../src/lib/apply-secret-placeholder-guard.test.ts"],"names":[],"mappings":""}
@@ -23,7 +23,7 @@ export interface ConfigApplySkippedTarget {
23
23
  config_id: string;
24
24
  config_slug: string;
25
25
  path: string;
26
- owner: typeof SESSION_RENDERER_OWNER_ID | "equivalent-profile-config" | "retired-provider-config";
26
+ owner: typeof SESSION_RENDERER_OWNER_ID | "equivalent-profile-config" | "retired-provider-config" | "unresolved-secret-placeholder";
27
27
  reason: string;
28
28
  }
29
29
  export interface ConfigApplyPreviewFailure {
@@ -1 +1 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/lib/apply.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAgB,MAAM,mBAAmB,CAAC;AAE3E,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAG1D,OAAO,EAGL,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAI7B,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK5C;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAsBrD;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9B;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,yBAAyB,GAAG,2BAA2B,GAAG,yBAAyB,CAAC;IAClG,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,QAAQ,EAAE,yBAAyB,EAAE,CAAC;CACvC;AAuHD,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,WAAW,CAAC,CA+BtB;AA2ED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,WAAW,EAAE,CAAC,CAMxB;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,kBAAkB,CAAC,CA4B7B;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAM,GACtC,OAAO,CAAC,kBAAkB,CAAC,CAE7B"}
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/lib/apply.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAA8B,MAAM,mBAAmB,CAAC;AAEzF,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAQ1D,OAAO,EAGL,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAM7B,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK5C;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAsBrD;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9B;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,yBAAyB,GAAG,2BAA2B,GAAG,yBAAyB,GAAG,+BAA+B,CAAC;IACpI,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,OAAO,EAAE,wBAAwB,EAAE,CAAC;IACpC,QAAQ,EAAE,yBAAyB,EAAE,CAAC;CACvC;AA8PD,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,WAAW,CAAC,CA+BtB;AA2ED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,WAAW,EAAE,CAAC,CAMxB;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,kBAAkB,CAAC,CAiC7B;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAM,GACtC,OAAO,CAAC,kBAAkB,CAAC,CAE7B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=create-config-target-guard.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-config-target-guard.test.d.ts","sourceRoot":"","sources":["../../src/mcp/create-config-target-guard.test.ts"],"names":[],"mappings":""}
package/dist/mcp/index.js CHANGED
@@ -578,9 +578,6 @@ function templateizeMachineContent(content, machine) {
578
578
  next = next.replace(/~\/workspace/g, "{{WORKSPACE_ROOT}}").replace(/~\/Workspace/g, "{{WORKSPACE_ROOT}}").replace(/\/opt\/homebrew\/bin\/bun/g, "{{BUN_PATH}}");
579
579
  return { content: next, changed: next !== content };
580
580
  }
581
- function renderMachineAwareContent(content, variables) {
582
- return isTemplate(content) ? renderTemplate(content, variables) : content;
583
- }
584
581
  function renderMachineAwareContentPreview(content, variables) {
585
582
  return isTemplate(content) ? renderTemplatePreview(content, variables) : { content, unresolved: [] };
586
583
  }
@@ -5894,8 +5891,9 @@ function normalizeTargetPath(p) {
5894
5891
  }
5895
5892
  }
5896
5893
  async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
5897
- const renderedTarget = opts.vars ? renderForApply(targetPath, opts.vars, opts.dryRun === true) : { content: targetPath, unresolved: [] };
5898
- const rendered = opts.vars ? renderForApply(content, opts.vars, opts.dryRun === true) : { content, unresolved: [] };
5894
+ const variables = opts.vars ?? {};
5895
+ const renderedTarget = renderForApply(targetPath, variables);
5896
+ const rendered = renderForApply(content, variables);
5899
5897
  const renderedTargetPath = renderedTarget.content;
5900
5898
  const renderedContent = rendered.content;
5901
5899
  const targetAgent = meta.agent ?? config.agent;
@@ -5934,13 +5932,42 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
5934
5932
  ...meta
5935
5933
  };
5936
5934
  }
5937
- function renderForApply(content, variables, preview) {
5938
- if (preview)
5939
- return renderMachineAwareContentPreview(content, variables);
5940
- return {
5941
- content: renderMachineAwareContent(content, variables),
5942
- unresolved: []
5943
- };
5935
+ function renderForApply(content, variables) {
5936
+ return renderMachineAwareContentPreview(content, variables);
5937
+ }
5938
+ function wouldDestroyACredential(targetPath, renderedContent, format) {
5939
+ const secretTokens = parseTemplateVars(renderedContent).filter(isSecretVarName);
5940
+ if (secretTokens.length === 0)
5941
+ return [];
5942
+ let current;
5943
+ try {
5944
+ const path = expandPath(targetPath);
5945
+ if (!existsSync4(path))
5946
+ return [];
5947
+ current = readFileSync2(path, "utf-8");
5948
+ } catch {
5949
+ return secretTokens;
5950
+ }
5951
+ if (current === renderedContent)
5952
+ return [];
5953
+ if (scanSecrets(current, format).length > 0)
5954
+ return secretTokens;
5955
+ return secretTokens.filter((name) => countPlaceholder(current, name) < countPlaceholder(renderedContent, name));
5956
+ }
5957
+ function countPlaceholder(content, name) {
5958
+ return content.match(new RegExp(`\\{\\{${name}(?::[^}]*)?\\}\\}`, "g"))?.length ?? 0;
5959
+ }
5960
+ async function resolveApplyVariables(opts) {
5961
+ const machine = detectMachineContext();
5962
+ let base;
5963
+ try {
5964
+ const store = opts.store ?? resolveConfigStore();
5965
+ const profile = await store.resolveProfileForMachine(machine);
5966
+ base = resolveProfileVariables(profile, machine);
5967
+ } catch {
5968
+ base = machineContextToVariables(machine);
5969
+ }
5970
+ return { ...base, ...opts.vars ?? {} };
5944
5971
  }
5945
5972
  function isAntigravityRuleTarget(agent, targetPath) {
5946
5973
  return agent === "antigravity" && /\.(md|mdc|markdown)$/i.test(targetPath);
@@ -6025,7 +6052,8 @@ async function applyConfigs(configs, opts = {}) {
6025
6052
  return report.results;
6026
6053
  }
6027
6054
  async function applyConfigsWithReport(configs, opts = {}) {
6028
- const prepared = prepareConfigBatch(configs, opts);
6055
+ const effective = { ...opts, vars: await resolveApplyVariables(opts) };
6056
+ const prepared = prepareConfigBatch(configs, effective);
6029
6057
  if (prepared.failures.length > 0) {
6030
6058
  return {
6031
6059
  results: [],
@@ -6039,7 +6067,7 @@ async function applyConfigsWithReport(configs, opts = {}) {
6039
6067
  if (config.kind === "reference" || isRetiredOrUnsupportedConfigAgent(config.agent))
6040
6068
  continue;
6041
6069
  try {
6042
- results.push(await applyPreparedConfig(config, opts));
6070
+ results.push(await applyPreparedConfig(config, effective));
6043
6071
  } catch (error) {
6044
6072
  failures.push({
6045
6073
  config_id: config.id,
@@ -6079,7 +6107,27 @@ function prepareConfigBatch(configs, opts) {
6079
6107
  }
6080
6108
  return false;
6081
6109
  });
6082
- const { configs: deduplicated, duplicates } = deduplicateEquivalentProfileConfigs(activeConfigs, opts);
6110
+ const renderSafeConfigs = activeConfigs.filter((config) => {
6111
+ const behavior = canonicalConfigApplyBehavior(config, opts, activeConfigs);
6112
+ const targets = [
6113
+ ...behavior.primary ? [{ path: behavior.primary.path, content: behavior.primary.content }] : [],
6114
+ ...behavior.outputs.map((output) => ({ path: output.path, content: output.content }))
6115
+ ];
6116
+ const blocked = [...new Set(targets.flatMap((target) => wouldDestroyACredential(target.path, target.content, config.format)))].sort();
6117
+ if (blocked.length === 0)
6118
+ return true;
6119
+ for (const target of targets) {
6120
+ skipped.push({
6121
+ config_id: config.id,
6122
+ config_slug: config.slug,
6123
+ path: target.path,
6124
+ owner: "unresolved-secret-placeholder",
6125
+ reason: `no value for ${blocked.map((name) => `{{${name}}}`).join(", ")} and the file holds something else there; writing the placeholder would overwrite a live credential`
6126
+ });
6127
+ }
6128
+ return false;
6129
+ });
6130
+ const { configs: deduplicated, duplicates } = deduplicateEquivalentProfileConfigs(renderSafeConfigs, opts);
6083
6131
  for (const duplicate of duplicates) {
6084
6132
  for (const target of configTargets(duplicate, opts, activeConfigs)) {
6085
6133
  skipped.push({
@@ -6231,6 +6279,8 @@ var init_apply = __esm(() => {
6231
6279
  init_machine();
6232
6280
  init_session_render();
6233
6281
  init_session_render_ownership();
6282
+ init_redact();
6283
+ init_template();
6234
6284
  init_transforms();
6235
6285
  });
6236
6286
 
@@ -6858,7 +6908,7 @@ var init_sync_dir = __esm(() => {
6858
6908
  var require_package = __commonJS((exports, module) => {
6859
6909
  module.exports = {
6860
6910
  name: "@hasna/instructions",
6861
- version: "0.4.13",
6911
+ version: "0.4.15",
6862
6912
  description: "AI coding agent instruction & configuration manager \u2014 store, version, apply, and share all your AI coding configs. CLI + MCP + HTTP API (instructions-serve) + generated SDK + Dashboard.",
6863
6913
  type: "module",
6864
6914
  main: "dist/index.js",
@@ -6897,7 +6947,7 @@ var require_package = __commonJS((exports, module) => {
6897
6947
  "dev:mcp": "bun run src/mcp/index.ts",
6898
6948
  "dev:serve": "bun run src/server/index.ts",
6899
6949
  seed: "bun run scripts/seed.ts",
6900
- prepublishOnly: "bun run build",
6950
+ prepublishOnly: "bun run scripts/check-publish-hold.ts && bun run build",
6901
6951
  postinstall: "mkdir -p $HOME/.hasna/instructions $HOME/.hasna/instructions/backups 2>/dev/null || true"
6902
6952
  },
6903
6953
  keywords: [
@@ -6958,11 +7008,26 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
6958
7008
  // src/mcp/server.ts
6959
7009
  init_config_store();
6960
7010
  init_apply();
6961
- init_sync_dir();
6962
- init_machine();
6963
7011
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6964
7012
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
6965
7013
 
7014
+ // src/lib/config-target-identity.ts
7015
+ init_apply();
7016
+ function findConfigsByTargetPath(configs, targetPath) {
7017
+ const wanted = normalizeTargetPath(targetPath);
7018
+ return configs.filter((config) => {
7019
+ if (config.kind === "reference")
7020
+ return false;
7021
+ if (!config.target_path)
7022
+ return false;
7023
+ return normalizeTargetPath(config.target_path) === wanted;
7024
+ });
7025
+ }
7026
+
7027
+ // src/mcp/server.ts
7028
+ init_sync_dir();
7029
+ init_machine();
7030
+
6966
7031
  // src/lib/compact-output.ts
6967
7032
  var DEFAULT_LIST_LIMIT = 20;
6968
7033
  var MAX_LIST_LIMIT = 100;
@@ -7064,7 +7129,7 @@ function summarizeApplyResult(result) {
7064
7129
  var TOOL_DOCS = {
7065
7130
  list_configs: "List configs. Params: category?, agent?, kind?, search?, limit?, cursor?, verbose?. Defaults to a paged compact envelope without content; use get_config for full content.",
7066
7131
  get_config: "Get a config by id or slug. Returns full config including content.",
7067
- create_config: "Create a new config. Required: name, content, category. Optional: agent, target_path, outputs, kind, format, tags, description, is_template.",
7132
+ create_config: "Create a new config. Required: name, content, category. Optional: agent, target_path, outputs, kind, format, tags, description, is_template. Refuses when target_path is already tracked by another config (one target path, one row) \u2014 use update_config on the owning row, or delete_config first. kind:'reference' owns no target path and is exempt.",
7068
7133
  update_config: "Update a config by id or slug. Optional: content, name, tags, description, category, agent, target_path, outputs.",
7069
7134
  apply_config: "Apply a config through the shared ownership gate. Params: id_or_slug, dry_run?, verbose?. Returns results plus session-renderer-owned targets that were skipped.",
7070
7135
  sync_directory: "Sync a directory with the DB. Params: dir, direction ('from_disk'|'to_disk'). Returns sync result.",
@@ -7145,14 +7210,24 @@ function buildServer() {
7145
7210
  return ok(c);
7146
7211
  }
7147
7212
  case "create_config": {
7213
+ const kind = args["kind"] || undefined;
7214
+ const targetPath = args["target_path"] || undefined;
7215
+ if (targetPath && kind !== "reference") {
7216
+ const owners = findConfigsByTargetPath(await store.listConfigs(), targetPath);
7217
+ if (owners.length > 0) {
7218
+ const named = owners.map((owner) => `${owner.slug} (${owner.id})`).join(", ");
7219
+ const collision = owners.length > 1 ? ` ${owners.length} rows already collide on this path \u2014 apply order between them is undefined.` : "";
7220
+ return err(`${targetPath} is already tracked by: ${named}.${collision}` + ` Use update_config on that row to refresh it in place, or delete_config first.`);
7221
+ }
7222
+ }
7148
7223
  const c = await store.createConfig({
7149
7224
  name: args["name"],
7150
7225
  content: args["content"],
7151
7226
  category: args["category"],
7152
7227
  agent: args["agent"] || undefined,
7153
- target_path: args["target_path"] || undefined,
7228
+ target_path: targetPath,
7154
7229
  outputs: args["outputs"],
7155
- kind: args["kind"] || undefined,
7230
+ kind,
7156
7231
  format: args["format"] || undefined,
7157
7232
  tags: args["tags"] || undefined,
7158
7233
  description: args["description"] || undefined,
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AA2EnE,wBAAgB,WAAW,IAAI,MAAM,CA0RpC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AA4EnE,wBAAgB,WAAW,IAAI,MAAM,CAyTpC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/instructions",
3
- "version": "0.4.13",
3
+ "version": "0.4.15",
4
4
  "description": "AI coding agent instruction & configuration manager \u2014 store, version, apply, and share all your AI coding configs. CLI + MCP + HTTP API (instructions-serve) + generated SDK + Dashboard.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,7 +39,7 @@
39
39
  "dev:mcp": "bun run src/mcp/index.ts",
40
40
  "dev:serve": "bun run src/server/index.ts",
41
41
  "seed": "bun run scripts/seed.ts",
42
- "prepublishOnly": "bun run build",
42
+ "prepublishOnly": "bun run scripts/check-publish-hold.ts && bun run build",
43
43
  "postinstall": "mkdir -p $HOME/.hasna/instructions $HOME/.hasna/instructions/backups 2>/dev/null || true"
44
44
  },
45
45
  "keywords": [